aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Emby.Drawing/ImageProcessor.cs13
-rw-r--r--MediaBrowser.Api/ApiEntryPoint.cs60
-rw-r--r--MediaBrowser.Api/Dlna/DlnaServerService.cs8
-rw-r--r--MediaBrowser.Api/FilterService.cs9
-rw-r--r--MediaBrowser.Api/GamesService.cs7
-rw-r--r--MediaBrowser.Api/Images/RemoteImageService.cs3
-rw-r--r--MediaBrowser.Api/ItemUpdateService.cs31
-rw-r--r--MediaBrowser.Api/Library/ChapterService.cs30
-rw-r--r--MediaBrowser.Api/Library/LibraryService.cs32
-rw-r--r--MediaBrowser.Api/Library/LibraryStructureService.cs46
-rw-r--r--MediaBrowser.Api/LiveTv/LiveTvService.cs137
-rw-r--r--MediaBrowser.Api/MediaBrowser.Api.csproj10
-rw-r--r--MediaBrowser.Api/Movies/MoviesService.cs22
-rw-r--r--MediaBrowser.Api/PackageService.cs8
-rw-r--r--MediaBrowser.Api/Playback/BaseStreamingService.cs86
-rw-r--r--MediaBrowser.Api/Playback/Hls/BaseHlsService.cs60
-rw-r--r--MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs8
-rw-r--r--MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs4
-rw-r--r--MediaBrowser.Api/Playback/Hls/VideoHlsService.cs2
-rw-r--r--MediaBrowser.Api/Playback/MediaInfoService.cs4
-rw-r--r--MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs43
-rw-r--r--MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs36
-rw-r--r--MediaBrowser.Api/Playback/StreamState.cs25
-rw-r--r--MediaBrowser.Api/Reports/Data/ReportBuilder.cs4
-rw-r--r--MediaBrowser.Api/Reports/Stat/ReportStatBuilder.cs25
-rw-r--r--MediaBrowser.Api/SimilarItemsHelper.cs3
-rw-r--r--MediaBrowser.Api/StartupWizardService.cs11
-rw-r--r--MediaBrowser.Api/Subtitles/SubtitleService.cs2
-rw-r--r--MediaBrowser.Api/TvShowsService.cs14
-rw-r--r--MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs1
-rw-r--r--MediaBrowser.Api/UserLibrary/ItemsService.cs36
-rw-r--r--MediaBrowser.Common.Implementations/BaseApplicationHost.cs8
-rw-r--r--MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs22
-rw-r--r--MediaBrowser.Common.Implementations/IO/MemoryStreamProvider.cs31
-rw-r--r--MediaBrowser.Common.Implementations/Logging/NLogger.cs10
-rw-r--r--MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj19
-rw-r--r--MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs4
-rw-r--r--MediaBrowser.Common.Implementations/Security/PluginSecurityManager.cs6
-rw-r--r--MediaBrowser.Common.Implementations/Updates/GithubUpdater.cs6
-rw-r--r--MediaBrowser.Common.Implementations/Updates/InstallationManager.cs12
-rw-r--r--MediaBrowser.Common.Implementations/packages.config5
-rw-r--r--MediaBrowser.Common/IO/IMemoryStreamProvider.cs11
-rw-r--r--MediaBrowser.Common/MediaBrowser.Common.csproj1
-rw-r--r--MediaBrowser.Common/Updates/IInstallationManager.cs2
-rw-r--r--MediaBrowser.Controller/Channels/IChannelManager.cs10
-rw-r--r--MediaBrowser.Controller/Chapters/ChapterResponse.cs19
-rw-r--r--MediaBrowser.Controller/Chapters/ChapterSearchRequest.cs31
-rw-r--r--MediaBrowser.Controller/Chapters/IChapterManager.cs47
-rw-r--r--MediaBrowser.Controller/Chapters/IChapterProvider.cs39
-rw-r--r--MediaBrowser.Controller/Dlna/IDeviceDiscovery.cs14
-rw-r--r--MediaBrowser.Controller/Dlna/ISsdpHandler.cs1
-rw-r--r--MediaBrowser.Controller/Entities/AggregateFolder.cs15
-rw-r--r--MediaBrowser.Controller/Entities/Audio/Audio.cs31
-rw-r--r--MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs16
-rw-r--r--MediaBrowser.Controller/Entities/Audio/MusicArtist.cs18
-rw-r--r--MediaBrowser.Controller/Entities/BaseItem.cs97
-rw-r--r--MediaBrowser.Controller/Entities/CollectionFolder.cs42
-rw-r--r--MediaBrowser.Controller/Entities/Folder.cs88
-rw-r--r--MediaBrowser.Controller/Entities/Game.cs15
-rw-r--r--MediaBrowser.Controller/Entities/GameSystem.cs9
-rw-r--r--MediaBrowser.Controller/Entities/IArchivable.cs8
-rw-r--r--MediaBrowser.Controller/Entities/IHasImages.cs6
-rw-r--r--MediaBrowser.Controller/Entities/IHasMetadata.cs3
-rw-r--r--MediaBrowser.Controller/Entities/IHasProductionLocations.cs34
-rw-r--r--MediaBrowser.Controller/Entities/IHasTaglines.cs39
-rw-r--r--MediaBrowser.Controller/Entities/IHasThemeMedia.cs23
-rw-r--r--MediaBrowser.Controller/Entities/IHasUserData.cs2
-rw-r--r--MediaBrowser.Controller/Entities/InternalItemsQuery.cs49
-rw-r--r--MediaBrowser.Controller/Entities/Movies/Movie.cs24
-rw-r--r--MediaBrowser.Controller/Entities/MusicVideo.cs15
-rw-r--r--MediaBrowser.Controller/Entities/Photo.cs14
-rw-r--r--MediaBrowser.Controller/Entities/PhotoAlbum.cs9
-rw-r--r--MediaBrowser.Controller/Entities/TV/Season.cs2
-rw-r--r--MediaBrowser.Controller/Entities/TV/Series.cs5
-rw-r--r--MediaBrowser.Controller/Entities/Trailer.cs16
-rw-r--r--MediaBrowser.Controller/Entities/UserRootFolder.cs9
-rw-r--r--MediaBrowser.Controller/Entities/UserView.cs10
-rw-r--r--MediaBrowser.Controller/Entities/UserViewBuilder.cs18
-rw-r--r--MediaBrowser.Controller/Entities/Video.cs48
-rw-r--r--MediaBrowser.Controller/Library/ILibraryManager.cs7
-rw-r--r--MediaBrowser.Controller/Library/IMediaSourceManager.cs16
-rw-r--r--MediaBrowser.Controller/Library/IMediaSourceProvider.cs6
-rw-r--r--MediaBrowser.Controller/LiveTv/ILiveTvManager.cs38
-rw-r--r--MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs1
-rw-r--r--MediaBrowser.Controller/LiveTv/ILiveTvService.cs11
-rw-r--r--MediaBrowser.Controller/LiveTv/ITunerHost.cs7
-rw-r--r--MediaBrowser.Controller/LiveTv/LiveStream.cs45
-rw-r--r--MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs3
-rw-r--r--MediaBrowser.Controller/LiveTv/LiveTvChannel.cs58
-rw-r--r--MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs21
-rw-r--r--MediaBrowser.Controller/LiveTv/ProgramInfo.cs2
-rw-r--r--MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs10
-rw-r--r--MediaBrowser.Controller/LiveTv/TimerInfo.cs38
-rw-r--r--MediaBrowser.Controller/MediaBrowser.Controller.csproj12
-rw-r--r--MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs6
-rw-r--r--MediaBrowser.Controller/MediaEncoding/MediaInfoRequest.cs1
-rw-r--r--MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs4
-rw-r--r--MediaBrowser.Controller/Net/IHttpResultFactory.cs8
-rw-r--r--MediaBrowser.Controller/Playlists/Playlist.cs23
-rw-r--r--MediaBrowser.Controller/Power/IPowerManagement.cs13
-rw-r--r--MediaBrowser.Controller/Providers/BaseItemXmlParser.cs21
-rw-r--r--MediaBrowser.Controller/Providers/IImageFileSaver.cs20
-rw-r--r--MediaBrowser.Controller/Providers/IImageSaver.cs11
-rw-r--r--MediaBrowser.Controller/Providers/IProviderManager.cs7
-rw-r--r--MediaBrowser.Controller/Providers/ItemInfo.cs2
-rw-r--r--MediaBrowser.Controller/Providers/MetadataResult.cs4
-rw-r--r--MediaBrowser.Controller/RelatedMedia/IRelatedMediaProvider.cs11
-rw-r--r--MediaBrowser.Dlna/ContentDirectory/ContentDirectoryBrowser.cs3
-rw-r--r--MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs61
-rw-r--r--MediaBrowser.Dlna/Didl/DidlBuilder.cs6
-rw-r--r--MediaBrowser.Dlna/Eventing/EventManager.cs4
-rw-r--r--MediaBrowser.Dlna/Main/DlnaEntryPoint.cs113
-rw-r--r--MediaBrowser.Dlna/MediaBrowser.Dlna.csproj15
-rw-r--r--MediaBrowser.Dlna/PlayTo/Device.cs2
-rw-r--r--MediaBrowser.Dlna/PlayTo/PlayToController.cs23
-rw-r--r--MediaBrowser.Dlna/PlayTo/PlayToManager.cs26
-rw-r--r--MediaBrowser.Dlna/PlayTo/SsdpHttpClient.cs9
-rw-r--r--MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs217
-rw-r--r--MediaBrowser.Dlna/Ssdp/SsdpHandler.cs383
-rw-r--r--MediaBrowser.Dlna/Ssdp/SsdpHelper.cs45
-rw-r--r--MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs2
-rw-r--r--MediaBrowser.LocalMetadata/Savers/GameXmlSaver.cs2
-rw-r--r--MediaBrowser.LocalMetadata/Savers/PersonXmlSaver.cs4
-rw-r--r--MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs39
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs39
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs24
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs15
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs3
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs15
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs148
-rw-r--r--MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs11
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs10
-rw-r--r--MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj15
-rw-r--r--MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj17
-rw-r--r--MediaBrowser.Model/Chapters/ChapterProviderInfo.cs8
-rw-r--r--MediaBrowser.Model/Chapters/RemoteChapterInfo.cs18
-rw-r--r--MediaBrowser.Model/Chapters/RemoteChapterResult.cs48
-rw-r--r--MediaBrowser.Model/Configuration/AutoOnOff.cs10
-rw-r--r--MediaBrowser.Model/Configuration/ChapterOptions.cs14
-rw-r--r--MediaBrowser.Model/Configuration/DlnaOptions.cs1
-rw-r--r--MediaBrowser.Model/Configuration/LibraryOptions.cs13
-rw-r--r--MediaBrowser.Model/Configuration/ServerConfiguration.cs15
-rw-r--r--MediaBrowser.Model/Dlna/ConditionProcessor.cs5
-rw-r--r--MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs6
-rw-r--r--MediaBrowser.Model/Dlna/DeviceProfile.cs5
-rw-r--r--MediaBrowser.Model/Dlna/ProfileConditionValue.cs3
-rw-r--r--MediaBrowser.Model/Dlna/StreamBuilder.cs10
-rw-r--r--MediaBrowser.Model/Dlna/StreamInfo.cs30
-rw-r--r--MediaBrowser.Model/Dto/BaseItemDto.cs9
-rw-r--r--MediaBrowser.Model/Dto/ItemCounts.cs1
-rw-r--r--MediaBrowser.Model/Dto/MediaSourceInfo.cs7
-rw-r--r--MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs3
-rw-r--r--MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs36
-rw-r--r--MediaBrowser.Model/LiveTv/ProgramQuery.cs8
-rw-r--r--MediaBrowser.Model/LiveTv/RecommendedProgramQuery.cs6
-rw-r--r--MediaBrowser.Model/LiveTv/RecordingQuery.cs1
-rw-r--r--MediaBrowser.Model/LiveTv/RecordingStatus.cs1
-rw-r--r--MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs47
-rw-r--r--MediaBrowser.Model/LiveTv/TimerInfoDto.cs2
-rw-r--r--MediaBrowser.Model/LiveTv/TimerQuery.cs2
-rw-r--r--MediaBrowser.Model/MediaBrowser.Model.csproj5
-rw-r--r--MediaBrowser.Model/MediaInfo/MediaProtocol.cs3
-rw-r--r--MediaBrowser.Model/Notifications/NotificationType.cs1
-rw-r--r--MediaBrowser.Model/Providers/RemoteSearchResult.cs3
-rw-r--r--MediaBrowser.Model/Querying/ItemFields.cs6
-rw-r--r--MediaBrowser.Model/System/SystemInfo.cs2
-rw-r--r--MediaBrowser.Model/Updates/PackageInfo.cs6
-rw-r--r--MediaBrowser.Model/Updates/PackageType.cs21
-rw-r--r--MediaBrowser.Mono.sln18
-rw-r--r--MediaBrowser.Providers/Chapters/ChapterManager.cs220
-rw-r--r--MediaBrowser.Providers/GameGenres/GameGenreImageProvider.cs4
-rw-r--r--MediaBrowser.Providers/Genres/GenreImageProvider.cs6
-rw-r--r--MediaBrowser.Providers/Manager/ImageSaver.cs30
-rw-r--r--MediaBrowser.Providers/Manager/ItemImageProvider.cs30
-rw-r--r--MediaBrowser.Providers/Manager/MetadataService.cs88
-rw-r--r--MediaBrowser.Providers/Manager/ProviderManager.cs21
-rw-r--r--MediaBrowser.Providers/Manager/ProviderUtils.cs30
-rw-r--r--MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs2
-rw-r--r--MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs7
-rw-r--r--MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs73
-rw-r--r--MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs2
-rw-r--r--MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs90
-rw-r--r--MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs7
-rw-r--r--MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs30
-rw-r--r--MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs36
-rw-r--r--MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs60
-rw-r--r--MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs11
-rw-r--r--MediaBrowser.Providers/Omdb/OmdbItemProvider.cs8
-rw-r--r--MediaBrowser.Providers/People/MovieDbPersonProvider.cs6
-rw-r--r--MediaBrowser.Providers/People/PersonMetadataService.cs8
-rw-r--r--MediaBrowser.Providers/Studios/StudiosImageProvider.cs4
-rw-r--r--MediaBrowser.Providers/TV/MissingEpisodeProvider.cs2
-rw-r--r--MediaBrowser.Providers/TV/Omdb/OmdbEpisodeProvider.cs3
-rw-r--r--MediaBrowser.Providers/TV/SeriesPostScanTask.cs31
-rw-r--r--MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeProvider.cs7
-rw-r--r--MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeriesProvider.cs25
-rw-r--r--MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs12
-rw-r--r--MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs17
-rw-r--r--MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs29
-rw-r--r--MediaBrowser.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs4
-rw-r--r--MediaBrowser.Server.Implementations/Channels/ChannelManager.cs92
-rw-r--r--MediaBrowser.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs2
-rw-r--r--MediaBrowser.Server.Implementations/Collections/CollectionManager.cs40
-rw-r--r--MediaBrowser.Server.Implementations/Connect/ConnectEntryPoint.cs3
-rw-r--r--MediaBrowser.Server.Implementations/Connect/ConnectManager.cs51
-rw-r--r--MediaBrowser.Server.Implementations/Dto/DtoService.cs63
-rw-r--r--MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs66
-rw-r--r--MediaBrowser.Server.Implementations/EntryPoints/Notifications/Notifications.cs17
-rw-r--r--MediaBrowser.Server.Implementations/EntryPoints/UsageReporter.cs6
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs19
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs31
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/NativeWebSocket.cs240
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs18
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/ServerFactory.cs10
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/SocketSharp/RequestMono.cs8
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs7
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs9
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs18
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs37
-rw-r--r--MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs20
-rw-r--r--MediaBrowser.Server.Implementations/Intros/DefaultIntroProvider.cs7
-rw-r--r--MediaBrowser.Server.Implementations/Library/CoreResolutionIgnoreRule.cs3
-rw-r--r--MediaBrowser.Server.Implementations/Library/LibraryManager.cs324
-rw-r--r--MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs104
-rw-r--r--MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs2
-rw-r--r--MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs21
-rw-r--r--MediaBrowser.Server.Implementations/Library/Resolvers/FolderResolver.cs1
-rw-r--r--MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs30
-rw-r--r--MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs35
-rw-r--r--MediaBrowser.Server.Implementations/Library/SearchEngine.cs1
-rw-r--r--MediaBrowser.Server.Implementations/Library/UserDataManager.cs29
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs65
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs977
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs113
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs47
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs72
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs19
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/LiveStreamHelper.cs110
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs38
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs614
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs30
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs221
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunDiscovery.cs12
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs127
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunLiveStream.cs145
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs19
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs96
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/QueueStream.cs93
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs11
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpHost.cs10
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Core/en-US.json1
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Ratings/de.txt5
-rw-r--r--MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj25
-rw-r--r--MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs30
-rw-r--r--MediaBrowser.Server.Implementations/News/NewsEntryPoint.cs3
-rw-r--r--MediaBrowser.Server.Implementations/Notifications/CoreNotificationTypes.cs7
-rw-r--r--MediaBrowser.Server.Implementations/Persistence/DataExtensions.cs13
-rw-r--r--MediaBrowser.Server.Implementations/Persistence/SqliteDisplayPreferencesRepository.cs14
-rw-r--r--MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs947
-rw-r--r--MediaBrowser.Server.Implementations/Persistence/SqliteUserRepository.cs9
-rw-r--r--MediaBrowser.Server.Implementations/Playlists/ManualPlaylistsFolder.cs10
-rw-r--r--MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs7
-rw-r--r--MediaBrowser.Server.Implementations/ServerManager/WebSocketConnection.cs72
-rw-r--r--MediaBrowser.Server.Implementations/Session/HttpSessionController.cs3
-rw-r--r--MediaBrowser.Server.Implementations/Session/SessionManager.cs60
-rw-r--r--MediaBrowser.Server.Implementations/Sync/SyncManager.cs7
-rw-r--r--MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs6
-rw-r--r--MediaBrowser.Server.Implementations/Sync/TargetDataProvider.cs7
-rw-r--r--MediaBrowser.Server.Implementations/app.config8
-rw-r--r--MediaBrowser.Server.Implementations/packages.config5
-rw-r--r--MediaBrowser.Server.Mac.sln16
-rw-r--r--MediaBrowser.Server.Mac/Emby.Server.Mac.csproj1090
-rw-r--r--MediaBrowser.Server.Mac/Native/BaseMonoApp.cs14
-rw-r--r--MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj5
-rw-r--r--MediaBrowser.Server.Mono/Native/BaseMonoApp.cs19
-rw-r--r--MediaBrowser.Server.Mono/app.config14
-rw-r--r--MediaBrowser.Server.Startup.Common/ApplicationHost.cs300
-rw-r--r--MediaBrowser.Server.Startup.Common/ApplicationPathHelper.cs8
-rw-r--r--MediaBrowser.Server.Startup.Common/INativeApp.cs9
-rw-r--r--MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj7
-rw-r--r--MediaBrowser.ServerApplication/MainStartup.cs98
-rw-r--r--MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj9
-rw-r--r--MediaBrowser.ServerApplication/Native/WindowsApp.cs17
-rw-r--r--MediaBrowser.ServerApplication/Native/WindowsPowerManagement.cs94
-rw-r--r--MediaBrowser.ServerApplication/packages.config2
-rw-r--r--MediaBrowser.WebDashboard/Api/DashboardService.cs90
-rw-r--r--MediaBrowser.WebDashboard/Api/PackageCreator.cs2
-rw-r--r--MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj30
-rw-r--r--MediaBrowser.XbmcMetadata/EntryPoint.cs10
-rw-r--r--MediaBrowser.XbmcMetadata/Images/XbmcImageSaver.cs272
-rw-r--r--MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj1
-rw-r--r--MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs26
-rw-r--r--MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs23
-rw-r--r--MediaBrowser.sln32
-rw-r--r--Mono.Nat/AbstractNatDevice.cs97
-rw-r--r--Mono.Nat/AsyncResults/AsyncResult.cs71
-rw-r--r--Mono.Nat/Enums/MapState.cs36
-rw-r--r--Mono.Nat/Enums/ProtocolType.cs36
-rw-r--r--Mono.Nat/EventArgs/DeviceEventArgs.cs45
-rw-r--r--Mono.Nat/Exceptions/MappingException.cs87
-rw-r--r--Mono.Nat/IMapper.cs50
-rw-r--r--Mono.Nat/INatDevice.cs62
-rw-r--r--Mono.Nat/ISearcher.cs51
-rw-r--r--Mono.Nat/Mapping.cs123
-rw-r--r--Mono.Nat/Mono.Nat.csproj104
-rw-r--r--Mono.Nat/NatProtocol.cs9
-rw-r--r--Mono.Nat/NatUtility.cs264
-rw-r--r--Mono.Nat/Pmp/AsyncResults/PortMapAsyncResult.cs52
-rw-r--r--Mono.Nat/Pmp/Mappers/PmpMapper.cs83
-rw-r--r--Mono.Nat/Pmp/Pmp.cs118
-rw-r--r--Mono.Nat/Pmp/PmpConstants.cs56
-rw-r--r--Mono.Nat/Pmp/PmpNatDevice.cs347
-rw-r--r--Mono.Nat/Pmp/Searchers/PmpSearcher.cs149
-rw-r--r--Mono.Nat/Properties/AssemblyInfo.cs31
-rw-r--r--Mono.Nat/Upnp/AsyncResults/GetAllMappingsAsyncResult.cs56
-rw-r--r--Mono.Nat/Upnp/AsyncResults/PortMapAsyncResult.cs75
-rw-r--r--Mono.Nat/Upnp/Mappers/UpnpMapper.cs110
-rw-r--r--Mono.Nat/Upnp/Messages/DiscoverDeviceMessage.cs60
-rw-r--r--Mono.Nat/Upnp/Messages/ErrorMessage.cs63
-rw-r--r--Mono.Nat/Upnp/Messages/GetServicesMessage.cs62
-rw-r--r--Mono.Nat/Upnp/Messages/Requests/CreatePortMappingMessage.cs75
-rw-r--r--Mono.Nat/Upnp/Messages/Requests/DeletePortMappingMessage.cs57
-rw-r--r--Mono.Nat/Upnp/Messages/Requests/GetExternalIPAddressMessage.cs51
-rw-r--r--Mono.Nat/Upnp/Messages/Requests/GetGenericPortMappingEntry.cs55
-rw-r--r--Mono.Nat/Upnp/Messages/Requests/GetSpecificPortMappingEntryMessage.cs60
-rw-r--r--Mono.Nat/Upnp/Messages/Responses/CreatePortMappingResponseMessage.cs46
-rw-r--r--Mono.Nat/Upnp/Messages/Responses/DeletePortMappingResponseMessage.cs44
-rw-r--r--Mono.Nat/Upnp/Messages/Responses/GetExternalIPAddressResponseMessage.cs53
-rw-r--r--Mono.Nat/Upnp/Messages/Responses/GetGenericPortMappingEntryResponseMessage.cs108
-rw-r--r--Mono.Nat/Upnp/Messages/UpnpMessage.cs132
-rw-r--r--Mono.Nat/Upnp/Searchers/UpnpSearcher.cs287
-rw-r--r--Mono.Nat/Upnp/Upnp.cs83
-rw-r--r--Mono.Nat/Upnp/UpnpNatDevice.cs651
-rw-r--r--Nuget/MediaBrowser.Common.Internal.nuspec8
-rw-r--r--Nuget/MediaBrowser.Common.nuspec2
-rw-r--r--Nuget/MediaBrowser.Server.Core.nuspec4
-rw-r--r--OpenSubtitlesHandler/Utilities.cs3
337 files changed, 10274 insertions, 5979 deletions
diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs
index 80ebbb719..e9f8f81f3 100644
--- a/Emby.Drawing/ImageProcessor.cs
+++ b/Emby.Drawing/ImageProcessor.cs
@@ -829,18 +829,7 @@ namespace Emby.Drawing
// Run the enhancers sequentially in order of priority
foreach (var enhancer in imageEnhancers)
{
- var typeName = enhancer.GetType().Name;
-
- try
- {
- await enhancer.EnhanceImageAsync(item, inputPath, outputPath, imageType, imageIndex).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- _logger.ErrorException("{0} failed enhancing {1}", ex, typeName, item.Name);
-
- throw;
- }
+ await enhancer.EnhanceImageAsync(item, inputPath, outputPath, imageType, imageIndex).ConfigureAwait(false);
// Feed the output into the next enhancer as input
inputPath = outputPath;
diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs
index 7c5f7cde4..c6e45c61a 100644
--- a/MediaBrowser.Api/ApiEntryPoint.cs
+++ b/MediaBrowser.Api/ApiEntryPoint.cs
@@ -8,6 +8,7 @@ using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Session;
using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
@@ -15,6 +16,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
+using MediaBrowser.Model.Dto;
namespace MediaBrowser.Api
{
@@ -43,7 +45,13 @@ namespace MediaBrowser.Api
private readonly IFileSystem _fileSystem;
private readonly IMediaSourceManager _mediaSourceManager;
- public readonly SemaphoreSlim TranscodingStartLock = new SemaphoreSlim(1, 1);
+ /// <summary>
+ /// The active transcoding jobs
+ /// </summary>
+ private readonly List<TranscodingJob> _activeTranscodingJobs = new List<TranscodingJob>();
+
+ private readonly Dictionary<string, SemaphoreSlim> _transcodingLocks =
+ new Dictionary<string, SemaphoreSlim>();
/// <summary>
/// Initializes a new instance of the <see cref="ApiEntryPoint" /> class.
@@ -66,6 +74,21 @@ namespace MediaBrowser.Api
_sessionManager.PlaybackStart += _sessionManager_PlaybackStart;
}
+ public SemaphoreSlim GetTranscodingLock(string outputPath)
+ {
+ lock (_transcodingLocks)
+ {
+ SemaphoreSlim result;
+ if (!_transcodingLocks.TryGetValue(outputPath, out result))
+ {
+ result = new SemaphoreSlim(1, 1);
+ _transcodingLocks[outputPath] = result;
+ }
+
+ return result;
+ }
+ }
+
private void _sessionManager_PlaybackStart(object sender, PlaybackProgressEventArgs e)
{
if (!string.IsNullOrWhiteSpace(e.PlaySessionId))
@@ -148,11 +171,6 @@ namespace MediaBrowser.Api
}
/// <summary>
- /// The active transcoding jobs
- /// </summary>
- private readonly List<TranscodingJob> _activeTranscodingJobs = new List<TranscodingJob>();
-
- /// <summary>
/// Called when [transcode beginning].
/// </summary>
/// <param name="path">The path.</param>
@@ -187,7 +205,8 @@ namespace MediaBrowser.Api
CancellationTokenSource = cancellationTokenSource,
Id = transcodingJobId,
PlaySessionId = playSessionId,
- LiveStreamId = liveStreamId
+ LiveStreamId = liveStreamId,
+ MediaSource = state.MediaSource
};
_activeTranscodingJobs.Add(job);
@@ -256,6 +275,11 @@ namespace MediaBrowser.Api
}
}
+ lock (_transcodingLocks)
+ {
+ _transcodingLocks.Remove(path);
+ }
+
if (!string.IsNullOrWhiteSpace(state.Request.DeviceId))
{
_sessionManager.ClearTranscodingInfo(state.Request.DeviceId);
@@ -281,6 +305,14 @@ namespace MediaBrowser.Api
}
}
+ public TranscodingJob GetTranscodingJob(string playSessionId)
+ {
+ lock (_activeTranscodingJobs)
+ {
+ return _activeTranscodingJobs.FirstOrDefault(j => string.Equals(j.PlaySessionId, playSessionId, StringComparison.OrdinalIgnoreCase));
+ }
+ }
+
/// <summary>
/// Called when [transcode begin request].
/// </summary>
@@ -487,6 +519,11 @@ namespace MediaBrowser.Api
}
}
+ lock (_transcodingLocks)
+ {
+ _transcodingLocks.Remove(job.Path);
+ }
+
lock (job.ProcessLock)
{
if (job.TranscodingThrottler != null)
@@ -530,7 +567,7 @@ namespace MediaBrowser.Api
{
try
{
- await _mediaSourceManager.CloseLiveStream(job.LiveStreamId, CancellationToken.None).ConfigureAwait(false);
+ await _mediaSourceManager.CloseLiveStream(job.LiveStreamId).ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -656,6 +693,7 @@ namespace MediaBrowser.Api
/// Gets or sets the path.
/// </summary>
/// <value>The path.</value>
+ public MediaSourceInfo MediaSource { get; set; }
public string Path { get; set; }
/// <summary>
/// Gets or sets the type.
@@ -751,12 +789,12 @@ namespace MediaBrowser.Api
{
if (KillTimer == null)
{
- Logger.Debug("Starting kill timer at {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
+ //Logger.Debug("Starting kill timer at {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
KillTimer = new Timer(callback, this, intervalMs, Timeout.Infinite);
}
else
{
- Logger.Debug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
+ //Logger.Debug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
KillTimer.Change(intervalMs, Timeout.Infinite);
}
}
@@ -775,7 +813,7 @@ namespace MediaBrowser.Api
{
var intervalMs = PingTimeout;
- Logger.Debug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
+ //Logger.Debug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
KillTimer.Change(intervalMs, Timeout.Infinite);
}
}
diff --git a/MediaBrowser.Api/Dlna/DlnaServerService.cs b/MediaBrowser.Api/Dlna/DlnaServerService.cs
index 4e7b1a7d5..b2c7e5532 100644
--- a/MediaBrowser.Api/Dlna/DlnaServerService.cs
+++ b/MediaBrowser.Api/Dlna/DlnaServerService.cs
@@ -7,6 +7,8 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.IO;
namespace MediaBrowser.Api.Dlna
{
@@ -109,13 +111,15 @@ namespace MediaBrowser.Api.Dlna
private readonly IMediaReceiverRegistrar _mediaReceiverRegistrar;
private const string XMLContentType = "text/xml; charset=UTF-8";
+ private readonly IMemoryStreamProvider _memoryStreamProvider;
- public DlnaServerService(IDlnaManager dlnaManager, IContentDirectory contentDirectory, IConnectionManager connectionManager, IMediaReceiverRegistrar mediaReceiverRegistrar)
+ public DlnaServerService(IDlnaManager dlnaManager, IContentDirectory contentDirectory, IConnectionManager connectionManager, IMediaReceiverRegistrar mediaReceiverRegistrar, IMemoryStreamProvider memoryStreamProvider)
{
_dlnaManager = dlnaManager;
_contentDirectory = contentDirectory;
_connectionManager = connectionManager;
_mediaReceiverRegistrar = mediaReceiverRegistrar;
+ _memoryStreamProvider = memoryStreamProvider;
}
public object Get(GetDescriptionXml request)
@@ -201,7 +205,7 @@ namespace MediaBrowser.Api.Dlna
{
using (var response = _dlnaManager.GetIcon(request.Filename))
{
- using (var ms = new MemoryStream())
+ using (var ms = _memoryStreamProvider.CreateNew())
{
response.Stream.CopyTo(ms);
diff --git a/MediaBrowser.Api/FilterService.cs b/MediaBrowser.Api/FilterService.cs
index b3b75359a..57aa5575f 100644
--- a/MediaBrowser.Api/FilterService.cs
+++ b/MediaBrowser.Api/FilterService.cs
@@ -4,6 +4,7 @@ using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Querying;
using ServiceStack;
using System;
+using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
@@ -104,7 +105,13 @@ namespace MediaBrowser.Api
MediaTypes = request.GetMediaTypes(),
IncludeItemTypes = request.GetIncludeItemTypes(),
Recursive = true,
- EnableTotalRecordCount = false
+ EnableTotalRecordCount = false,
+ DtoOptions = new Controller.Dto.DtoOptions
+ {
+ Fields = new List<ItemFields> { ItemFields.Genres, ItemFields.Tags },
+ EnableImages = false,
+ EnableUserData = false
+ }
};
return query;
diff --git a/MediaBrowser.Api/GamesService.cs b/MediaBrowser.Api/GamesService.cs
index 0953b95e6..efa69d333 100644
--- a/MediaBrowser.Api/GamesService.cs
+++ b/MediaBrowser.Api/GamesService.cs
@@ -200,6 +200,8 @@ namespace MediaBrowser.Api
(!string.IsNullOrWhiteSpace(request.UserId) ? user.RootFolder :
_libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id);
+ var dtoOptions = GetDtoOptions(request);
+
var itemsResult = _libraryManager.GetItemList(new InternalItemsQuery(user)
{
Limit = request.Limit,
@@ -207,12 +209,11 @@ namespace MediaBrowser.Api
{
typeof(Game).Name
},
- SimilarTo = item
+ SimilarTo = item,
+ DtoOptions = dtoOptions
}).ToList();
- var dtoOptions = GetDtoOptions(request);
-
var result = new QueryResult<BaseItemDto>
{
Items = (await _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user).ConfigureAwait(false)).ToArray(),
diff --git a/MediaBrowser.Api/Images/RemoteImageService.cs b/MediaBrowser.Api/Images/RemoteImageService.cs
index b21e54495..25d7639c6 100644
--- a/MediaBrowser.Api/Images/RemoteImageService.cs
+++ b/MediaBrowser.Api/Images/RemoteImageService.cs
@@ -273,7 +273,8 @@ namespace MediaBrowser.Api.Images
{
var result = await _httpClient.GetResponse(new HttpRequestOptions
{
- Url = url
+ Url = url,
+ BufferContent = false
}).ConfigureAwait(false);
diff --git a/MediaBrowser.Api/ItemUpdateService.cs b/MediaBrowser.Api/ItemUpdateService.cs
index cda7ce0c6..687a21a46 100644
--- a/MediaBrowser.Api/ItemUpdateService.cs
+++ b/MediaBrowser.Api/ItemUpdateService.cs
@@ -243,11 +243,7 @@ namespace MediaBrowser.Api
hasBudget.Revenue = request.Revenue;
}
- var hasOriginalTitle = item as IHasOriginalTitle;
- if (hasOriginalTitle != null)
- {
- hasOriginalTitle.OriginalTitle = hasOriginalTitle.OriginalTitle;
- }
+ item.OriginalTitle = string.IsNullOrWhiteSpace(request.OriginalTitle) ? null : request.OriginalTitle;
var hasCriticRating = item as IHasCriticRating;
if (hasCriticRating != null)
@@ -278,10 +274,9 @@ namespace MediaBrowser.Api
item.Tags = request.Tags;
- var hasTaglines = item as IHasTaglines;
- if (hasTaglines != null)
+ if (request.Taglines != null)
{
- hasTaglines.Taglines = request.Taglines;
+ item.Tagline = request.Taglines.FirstOrDefault();
}
var hasShortOverview = item as IHasShortOverview;
@@ -308,8 +303,6 @@ namespace MediaBrowser.Api
item.OfficialRating = string.IsNullOrWhiteSpace(request.OfficialRating) ? null : request.OfficialRating;
item.CustomRating = request.CustomRating;
- SetProductionLocations(item, request);
-
item.PreferredMetadataCountryCode = request.PreferredMetadataCountryCode;
item.PreferredMetadataLanguage = request.PreferredMetadataLanguage;
@@ -417,23 +410,5 @@ namespace MediaBrowser.Api
series.AirTime = request.AirTime;
}
}
-
- private void SetProductionLocations(BaseItem item, BaseItemDto request)
- {
- var hasProductionLocations = item as IHasProductionLocations;
-
- if (hasProductionLocations != null)
- {
- hasProductionLocations.ProductionLocations = request.ProductionLocations;
- }
-
- var person = item as Person;
- if (person != null)
- {
- person.PlaceOfBirth = request.ProductionLocations == null
- ? null
- : request.ProductionLocations.FirstOrDefault();
- }
- }
}
}
diff --git a/MediaBrowser.Api/Library/ChapterService.cs b/MediaBrowser.Api/Library/ChapterService.cs
deleted file mode 100644
index 6b8dd18f1..000000000
--- a/MediaBrowser.Api/Library/ChapterService.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-using MediaBrowser.Controller.Chapters;
-using MediaBrowser.Controller.Net;
-using ServiceStack;
-using System.Linq;
-
-namespace MediaBrowser.Api.Library
-{
- [Route("/Providers/Chapters", "GET")]
- public class GetChapterProviders : IReturnVoid
- {
- }
-
- [Authenticated]
- public class ChapterService : BaseApiService
- {
- private readonly IChapterManager _chapterManager;
-
- public ChapterService(IChapterManager chapterManager)
- {
- _chapterManager = chapterManager;
- }
-
- public object Get(GetChapterProviders request)
- {
- var result = _chapterManager.GetProviders().ToList();
-
- return ToOptimizedResult(result);
- }
- }
-}
diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs
index c6b637f01..e0dfcb9ad 100644
--- a/MediaBrowser.Api/Library/LibraryService.cs
+++ b/MediaBrowser.Api/Library/LibraryService.cs
@@ -835,14 +835,14 @@ namespace MediaBrowser.Api.Library
: (Folder)_libraryManager.RootFolder)
: _libraryManager.GetItemById(request.Id);
- while (GetThemeSongIds(item).Count == 0 && request.InheritFromParent && item.GetParent() != null)
+ while (item.ThemeSongIds.Count == 0 && request.InheritFromParent && item.GetParent() != null)
{
item = item.GetParent();
}
var dtoOptions = GetDtoOptions(request);
- var dtos = GetThemeSongIds(item).Select(_libraryManager.GetItemById)
+ var dtos = item.ThemeSongIds.Select(_libraryManager.GetItemById)
.Where(i => i != null)
.OrderBy(i => i.SortName)
.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item));
@@ -879,14 +879,14 @@ namespace MediaBrowser.Api.Library
: (Folder)_libraryManager.RootFolder)
: _libraryManager.GetItemById(request.Id);
- while (GetThemeVideoIds(item).Count == 0 && request.InheritFromParent && item.GetParent() != null)
+ while (item.ThemeVideoIds.Count == 0 && request.InheritFromParent && item.GetParent() != null)
{
item = item.GetParent();
}
var dtoOptions = GetDtoOptions(request);
- var dtos = GetThemeVideoIds(item).Select(_libraryManager.GetItemById)
+ var dtos = item.ThemeVideoIds.Select(_libraryManager.GetItemById)
.Where(i => i != null)
.OrderBy(i => i.SortName)
.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item));
@@ -901,30 +901,6 @@ namespace MediaBrowser.Api.Library
};
}
- private List<Guid> GetThemeVideoIds(BaseItem item)
- {
- var i = item as IHasThemeMedia;
-
- if (i != null)
- {
- return i.ThemeVideoIds;
- }
-
- return new List<Guid>();
- }
-
- private List<Guid> GetThemeSongIds(BaseItem item)
- {
- var i = item as IHasThemeMedia;
-
- if (i != null)
- {
- return i.ThemeSongIds;
- }
-
- return new List<Guid>();
- }
-
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
public object Get(GetYearIndex request)
diff --git a/MediaBrowser.Api/Library/LibraryStructureService.cs b/MediaBrowser.Api/Library/LibraryStructureService.cs
index 72966a7cd..e872061a7 100644
--- a/MediaBrowser.Api/Library/LibraryStructureService.cs
+++ b/MediaBrowser.Api/Library/LibraryStructureService.cs
@@ -112,6 +112,8 @@ namespace MediaBrowser.Api.Library
/// <value>The name.</value>
public string Path { get; set; }
+ public MediaPathInfo PathInfo { get; set; }
+
/// <summary>
/// Gets or sets a value indicating whether [refresh library].
/// </summary>
@@ -119,6 +121,18 @@ namespace MediaBrowser.Api.Library
public bool RefreshLibrary { get; set; }
}
+ [Route("/Library/VirtualFolders/Paths/Update", "POST")]
+ public class UpdateMediaPath : IReturnVoid
+ {
+ /// <summary>
+ /// Gets or sets the name.
+ /// </summary>
+ /// <value>The name.</value>
+ public string Name { get; set; }
+
+ public MediaPathInfo PathInfo { get; set; }
+ }
+
[Route("/Library/VirtualFolders/Paths", "DELETE")]
public class RemoveMediaPath : IReturnVoid
{
@@ -212,7 +226,12 @@ namespace MediaBrowser.Api.Library
{
var libraryOptions = request.LibraryOptions ?? new LibraryOptions();
- _libraryManager.AddVirtualFolder(request.Name, request.CollectionType, request.Paths, libraryOptions, request.RefreshLibrary);
+ if (request.Paths != null && request.Paths.Length > 0)
+ {
+ libraryOptions.PathInfos = request.Paths.Select(i => new MediaPathInfo { Path = i }).ToArray();
+ }
+
+ _libraryManager.AddVirtualFolder(request.Name, request.CollectionType, libraryOptions, request.RefreshLibrary);
}
/// <summary>
@@ -308,7 +327,16 @@ namespace MediaBrowser.Api.Library
try
{
- _libraryManager.AddMediaPath(request.Name, request.Path);
+ var mediaPath = request.PathInfo;
+
+ if (mediaPath == null)
+ {
+ mediaPath = new MediaPathInfo
+ {
+ Path = request.Path
+ };
+ }
+ _libraryManager.AddMediaPath(request.Name, mediaPath);
}
finally
{
@@ -333,6 +361,20 @@ namespace MediaBrowser.Api.Library
}
/// <summary>
+ /// Posts the specified request.
+ /// </summary>
+ /// <param name="request">The request.</param>
+ public void Post(UpdateMediaPath request)
+ {
+ if (string.IsNullOrWhiteSpace(request.Name))
+ {
+ throw new ArgumentNullException("request");
+ }
+
+ _libraryManager.UpdateMediaPath(request.Name, request.PathInfo);
+ }
+
+ /// <summary>
/// Deletes the specified request.
/// </summary>
/// <param name="request">The request.</param>
diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs
index 3ad0ec1ba..4217cd6ab 100644
--- a/MediaBrowser.Api/LiveTv/LiveTvService.cs
+++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs
@@ -12,9 +12,14 @@ using ServiceStack;
using System;
using System.Collections.Generic;
using System.Globalization;
+using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using CommonIO;
+using MediaBrowser.Api.Playback.Progressive;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Server.Implementations.LiveTv.EmbyTV;
namespace MediaBrowser.Api.LiveTv
{
@@ -44,6 +49,21 @@ namespace MediaBrowser.Api.LiveTv
[ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public int? StartIndex { get; set; }
+ [ApiMember(Name = "IsMovie", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")]
+ public bool? IsMovie { get; set; }
+
+ [ApiMember(Name = "IsSeries", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")]
+ public bool? IsSeries { get; set; }
+
+ [ApiMember(Name = "IsNews", Description = "Optional filter for news.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")]
+ public bool? IsNews { get; set; }
+
+ [ApiMember(Name = "IsKids", Description = "Optional filter for kids.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")]
+ public bool? IsKids { get; set; }
+
+ [ApiMember(Name = "IsSports", Description = "Optional filter for sports.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")]
+ public bool? IsSports { get; set; }
+
/// <summary>
/// The maximum number of items to return
/// </summary>
@@ -85,6 +105,26 @@ namespace MediaBrowser.Api.LiveTv
[ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
public bool? EnableUserData { get; set; }
+ public string SortBy { get; set; }
+
+ public SortOrder? SortOrder { get; set; }
+
+ /// <summary>
+ /// Gets the order by.
+ /// </summary>
+ /// <returns>IEnumerable{ItemSortBy}.</returns>
+ public string[] GetOrderBy()
+ {
+ var val = SortBy;
+
+ if (string.IsNullOrEmpty(val))
+ {
+ return new string[] { };
+ }
+
+ return val.Split(',');
+ }
+
public GetChannels()
{
AddCurrentProgram = true;
@@ -159,6 +199,7 @@ namespace MediaBrowser.Api.LiveTv
public bool? IsSeries { get; set; }
public bool? IsKids { get; set; }
public bool? IsSports { get; set; }
+ public bool? IsNews { get; set; }
public GetRecordings()
{
@@ -275,6 +316,8 @@ namespace MediaBrowser.Api.LiveTv
public string SeriesTimerId { get; set; }
public bool? IsActive { get; set; }
+
+ public bool? IsScheduled { get; set; }
}
[Route("/LiveTv/Programs", "GET,POST", Summary = "Gets available live tv epgs..")]
@@ -305,6 +348,12 @@ namespace MediaBrowser.Api.LiveTv
[ApiMember(Name = "IsMovie", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")]
public bool? IsMovie { get; set; }
+ [ApiMember(Name = "IsSeries", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")]
+ public bool? IsSeries { get; set; }
+
+ [ApiMember(Name = "IsNews", Description = "Optional filter for news.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")]
+ public bool? IsNews { get; set; }
+
[ApiMember(Name = "IsKids", Description = "Optional filter for kids.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")]
public bool? IsKids { get; set; }
@@ -340,6 +389,8 @@ namespace MediaBrowser.Api.LiveTv
[ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
public bool? EnableUserData { get; set; }
+ public string SeriesTimerId { get; set; }
+
/// <summary>
/// Fields to return within the items, in addition to basic information
/// </summary>
@@ -376,15 +427,21 @@ namespace MediaBrowser.Api.LiveTv
[ApiMember(Name = "HasAired", Description = "Optional. Filter by programs that have completed airing, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
public bool? HasAired { get; set; }
- [ApiMember(Name = "IsSports", Description = "Optional filter for sports.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")]
- public bool? IsSports { get; set; }
+ [ApiMember(Name = "IsSeries", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")]
+ public bool? IsSeries { get; set; }
- [ApiMember(Name = "IsMovie", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
+ [ApiMember(Name = "IsMovie", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")]
public bool? IsMovie { get; set; }
- [ApiMember(Name = "IsKids", Description = "Optional filter for kids.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
+ [ApiMember(Name = "IsNews", Description = "Optional filter for news.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")]
+ public bool? IsNews { get; set; }
+
+ [ApiMember(Name = "IsKids", Description = "Optional filter for kids.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")]
public bool? IsKids { get; set; }
+ [ApiMember(Name = "IsSports", Description = "Optional filter for sports.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")]
+ public bool? IsSports { get; set; }
+
[ApiMember(Name = "EnableImages", Description = "Optional, include image information in output", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
public bool? EnableImages { get; set; }
@@ -613,16 +670,30 @@ namespace MediaBrowser.Api.LiveTv
}
+ [Route("/LiveTv/LiveStreamFiles/{Id}/stream.{Container}", "GET", Summary = "Gets a live tv channel")]
+ public class GetLiveStreamFile
+ {
+ public string Id { get; set; }
+ public string Container { get; set; }
+ }
+
+ [Route("/LiveTv/LiveRecordings/{Id}/stream", "GET", Summary = "Gets a live tv channel")]
+ public class GetLiveRecordingFile
+ {
+ public string Id { get; set; }
+ }
+
public class LiveTvService : BaseApiService
{
private readonly ILiveTvManager _liveTvManager;
private readonly IUserManager _userManager;
- private readonly IConfigurationManager _config;
+ private readonly IServerConfigurationManager _config;
private readonly IHttpClient _httpClient;
private readonly ILibraryManager _libraryManager;
private readonly IDtoService _dtoService;
+ private readonly IFileSystem _fileSystem;
- public LiveTvService(ILiveTvManager liveTvManager, IUserManager userManager, IConfigurationManager config, IHttpClient httpClient, ILibraryManager libraryManager, IDtoService dtoService)
+ public LiveTvService(ILiveTvManager liveTvManager, IUserManager userManager, IServerConfigurationManager config, IHttpClient httpClient, ILibraryManager libraryManager, IDtoService dtoService, IFileSystem fileSystem)
{
_liveTvManager = liveTvManager;
_userManager = userManager;
@@ -630,6 +701,41 @@ namespace MediaBrowser.Api.LiveTv
_httpClient = httpClient;
_libraryManager = libraryManager;
_dtoService = dtoService;
+ _fileSystem = fileSystem;
+ }
+
+ public async Task<object> Get(GetLiveRecordingFile request)
+ {
+ var path = EmbyTV.Current.GetActiveRecordingPath(request.Id);
+
+ if (path == null)
+ {
+ throw new FileNotFoundException();
+ }
+
+ var outputHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+
+ outputHeaders["Content-Type"] = Model.Net.MimeTypes.GetMimeType(path);
+
+ var streamSource = new ProgressiveFileCopier(_fileSystem, path, outputHeaders, null, Logger, CancellationToken.None)
+ {
+ AllowEndOfFile = false
+ };
+ return ResultFactory.GetAsyncStreamWriter(streamSource);
+ }
+
+ public async Task<object> Get(GetLiveStreamFile request)
+ {
+ var directStreamProvider = (await EmbyTV.Current.GetLiveStream(request.Id).ConfigureAwait(false)) as IDirectStreamProvider;
+ var outputHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+
+ outputHeaders["Content-Type"] = Model.Net.MimeTypes.GetMimeType("file." + request.Container);
+
+ var streamSource = new ProgressiveFileCopier(directStreamProvider, outputHeaders, null, Logger, CancellationToken.None)
+ {
+ AllowEndOfFile = false
+ };
+ return ResultFactory.GetAsyncStreamWriter(streamSource);
}
public object Get(GetDefaultListingProvider request)
@@ -702,7 +808,8 @@ namespace MediaBrowser.Api.LiveTv
var response = await _httpClient.Get(new HttpRequestOptions
{
- Url = "https://json.schedulesdirect.org/20141201/available/countries"
+ Url = "https://json.schedulesdirect.org/20141201/available/countries",
+ BufferContent = false
}).ConfigureAwait(false);
@@ -786,6 +893,13 @@ namespace MediaBrowser.Api.LiveTv
IsLiked = request.IsLiked,
IsDisliked = request.IsDisliked,
EnableFavoriteSorting = request.EnableFavoriteSorting,
+ IsMovie = request.IsMovie,
+ IsSeries = request.IsSeries,
+ IsNews = request.IsNews,
+ IsKids = request.IsKids,
+ IsSports = request.IsSports,
+ SortBy = request.GetOrderBy(),
+ SortOrder = request.SortOrder ?? SortOrder.Ascending,
AddCurrentProgram = request.AddCurrentProgram
}, CancellationToken.None).ConfigureAwait(false);
@@ -868,9 +982,12 @@ namespace MediaBrowser.Api.LiveTv
query.Limit = request.Limit;
query.SortBy = (request.SortBy ?? String.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
query.SortOrder = request.SortOrder;
+ query.IsNews = request.IsNews;
query.IsMovie = request.IsMovie;
+ query.IsSeries = request.IsSeries;
query.IsKids = request.IsKids;
query.IsSports = request.IsSports;
+ query.SeriesTimerId = request.SeriesTimerId;
query.Genres = (request.Genres ?? String.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
var result = await _liveTvManager.GetPrograms(query, GetDtoOptions(request), CancellationToken.None).ConfigureAwait(false);
@@ -886,8 +1003,10 @@ namespace MediaBrowser.Api.LiveTv
IsAiring = request.IsAiring,
Limit = request.Limit,
HasAired = request.HasAired,
+ IsSeries = request.IsSeries,
IsMovie = request.IsMovie,
IsKids = request.IsKids,
+ IsNews = request.IsNews,
IsSports = request.IsSports,
EnableTotalRecordCount = request.EnableTotalRecordCount
};
@@ -919,6 +1038,7 @@ namespace MediaBrowser.Api.LiveTv
IsInProgress = request.IsInProgress,
EnableTotalRecordCount = request.EnableTotalRecordCount,
IsMovie = request.IsMovie,
+ IsNews = request.IsNews,
IsSeries = request.IsSeries,
IsKids = request.IsKids,
IsSports = request.IsSports
@@ -975,7 +1095,8 @@ namespace MediaBrowser.Api.LiveTv
{
ChannelId = request.ChannelId,
SeriesTimerId = request.SeriesTimerId,
- IsActive = request.IsActive
+ IsActive = request.IsActive,
+ IsScheduled = request.IsScheduled
}, CancellationToken.None).ConfigureAwait(false);
diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj
index 77949d179..d7091df56 100644
--- a/MediaBrowser.Api/MediaBrowser.Api.csproj
+++ b/MediaBrowser.Api/MediaBrowser.Api.csproj
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@@ -11,9 +11,10 @@
<AssemblyName>MediaBrowser.Api</AssemblyName>
<FileAlignment>512</FileAlignment>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
- <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ <TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
<ReleaseVersion>
</ReleaseVersion>
+ <TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@@ -79,7 +80,6 @@
<Compile Include="Dlna\DlnaService.cs" />
<Compile Include="FilterService.cs" />
<Compile Include="IHasDtoOptions.cs" />
- <Compile Include="Library\ChapterService.cs" />
<Compile Include="Playback\MediaInfoService.cs" />
<Compile Include="Playback\TranscodingThrottler.cs" />
<Compile Include="PlaylistService.cs" />
@@ -198,6 +198,10 @@
<Project>{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}</Project>
<Name>MediaBrowser.Model</Name>
</ProjectReference>
+ <ProjectReference Include="..\MediaBrowser.Server.Implementations\MediaBrowser.Server.Implementations.csproj">
+ <Project>{2e781478-814d-4a48-9d80-bff206441a65}</Project>
+ <Name>MediaBrowser.Server.Implementations</Name>
+ </ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
diff --git a/MediaBrowser.Api/Movies/MoviesService.cs b/MediaBrowser.Api/Movies/MoviesService.cs
index f153a0475..559bca755 100644
--- a/MediaBrowser.Api/Movies/MoviesService.cs
+++ b/MediaBrowser.Api/Movies/MoviesService.cs
@@ -156,18 +156,19 @@ namespace MediaBrowser.Api.Movies
itemTypes.Add(typeof(LiveTvProgram).Name);
}
+ var dtoOptions = GetDtoOptions(request);
+
var itemsResult = _libraryManager.GetItemList(new InternalItemsQuery(user)
{
Limit = request.Limit,
IncludeItemTypes = itemTypes.ToArray(),
IsMovie = true,
SimilarTo = item,
- EnableGroupByMetadataKey = true
+ EnableGroupByMetadataKey = true,
+ DtoOptions = dtoOptions
}).ToList();
- var dtoOptions = GetDtoOptions(request);
-
var result = new QueryResult<BaseItemDto>
{
Items = (await _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user).ConfigureAwait(false)).ToArray(),
@@ -198,7 +199,8 @@ namespace MediaBrowser.Api.Movies
Limit = 7,
ParentId = parentIdGuid,
Recursive = true,
- IsPlayed = true
+ IsPlayed = true,
+ DtoOptions = dtoOptions
};
var recentlyPlayedMovies = _libraryManager.GetItemList(query).ToList();
@@ -221,7 +223,8 @@ namespace MediaBrowser.Api.Movies
ExcludeItemIds = recentlyPlayedMovies.Select(i => i.Id.ToString("N")).ToArray(),
EnableGroupByMetadataKey = true,
ParentId = parentIdGuid,
- Recursive = true
+ Recursive = true,
+ DtoOptions = dtoOptions
}).ToList();
@@ -302,7 +305,8 @@ namespace MediaBrowser.Api.Movies
PersonTypes = new[] { PersonType.Director },
IncludeItemTypes = itemTypes.ToArray(),
IsMovie = true,
- EnableGroupByMetadataKey = true
+ EnableGroupByMetadataKey = true,
+ DtoOptions = dtoOptions
}).DistinctBy(i => i.GetProviderId(MetadataProviders.Imdb) ?? Guid.NewGuid().ToString("N"))
.Take(itemLimit)
@@ -339,7 +343,8 @@ namespace MediaBrowser.Api.Movies
Limit = itemLimit + 2,
IncludeItemTypes = itemTypes.ToArray(),
IsMovie = true,
- EnableGroupByMetadataKey = true
+ EnableGroupByMetadataKey = true,
+ DtoOptions = dtoOptions
}).DistinctBy(i => i.GetProviderId(MetadataProviders.Imdb) ?? Guid.NewGuid().ToString("N"))
.Take(itemLimit)
@@ -375,7 +380,8 @@ namespace MediaBrowser.Api.Movies
IncludeItemTypes = itemTypes.ToArray(),
IsMovie = true,
SimilarTo = item,
- EnableGroupByMetadataKey = true
+ EnableGroupByMetadataKey = true,
+ DtoOptions = dtoOptions
}).ToList();
diff --git a/MediaBrowser.Api/PackageService.cs b/MediaBrowser.Api/PackageService.cs
index 6d8378aae..dc7c4c6c1 100644
--- a/MediaBrowser.Api/PackageService.cs
+++ b/MediaBrowser.Api/PackageService.cs
@@ -46,7 +46,7 @@ namespace MediaBrowser.Api
/// </summary>
/// <value>The name.</value>
[ApiMember(Name = "PackageType", Description = "Optional package type filter (System/UserInstalled)", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public PackageType? PackageType { get; set; }
+ public string PackageType { get; set; }
[ApiMember(Name = "TargetSystems", Description = "Optional. Filter by target system type. Allows multiple, comma delimited.", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET", AllowMultiple = true)]
public string TargetSystems { get; set; }
@@ -72,7 +72,7 @@ namespace MediaBrowser.Api
/// </summary>
/// <value>The name.</value>
[ApiMember(Name = "PackageType", Description = "Package type filter (System/UserInstalled)", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
- public PackageType PackageType { get; set; }
+ public string PackageType { get; set; }
}
/// <summary>
@@ -149,12 +149,12 @@ namespace MediaBrowser.Api
{
var result = new List<PackageVersionInfo>();
- if (request.PackageType == PackageType.UserInstalled || request.PackageType == PackageType.All)
+ if (string.Equals(request.PackageType, "UserInstalled", StringComparison.OrdinalIgnoreCase) || string.Equals(request.PackageType, "All", StringComparison.OrdinalIgnoreCase))
{
result.AddRange(_installationManager.GetAvailablePluginUpdates(_appHost.ApplicationVersion, false, CancellationToken.None).Result.ToList());
}
- else if (request.PackageType == PackageType.System || request.PackageType == PackageType.All)
+ else if (string.Equals(request.PackageType, "System", StringComparison.OrdinalIgnoreCase) || string.Equals(request.PackageType, "All", StringComparison.OrdinalIgnoreCase))
{
var updateCheckResult = _appHost.CheckForApplicationUpdate(CancellationToken.None, new Progress<double>()).Result;
diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs
index 0b0b23e12..0e91c0b9e 100644
--- a/MediaBrowser.Api/Playback/BaseStreamingService.cs
+++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs
@@ -367,6 +367,8 @@ namespace MediaBrowser.Api.Playback
{
param += " -crf 23";
}
+
+ param += " -tune zerolatency";
}
else if (string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase))
@@ -461,13 +463,15 @@ namespace MediaBrowser.Api.Playback
var level = NormalizeTranscodingLevel(state.OutputVideoCodec, state.VideoRequest.Level);
// h264_qsv and h264_nvenc expect levels to be expressed as a decimal. libx264 supports decimal and non-decimal format
+ // also needed for libx264 due to https://trac.ffmpeg.org/ticket/3307
if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) ||
- string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase))
+ string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase))
{
switch (level)
{
case "30":
- param += " -level 3";
+ param += " -level 3.0";
break;
case "31":
param += " -level 3.1";
@@ -476,7 +480,7 @@ namespace MediaBrowser.Api.Playback
param += " -level 3.2";
break;
case "40":
- param += " -level 4";
+ param += " -level 4.0";
break;
case "41":
param += " -level 4.1";
@@ -485,7 +489,7 @@ namespace MediaBrowser.Api.Playback
param += " -level 4.2";
break;
case "50":
- param += " -level 5";
+ param += " -level 5.0";
break;
case "51":
param += " -level 5.1";
@@ -767,7 +771,20 @@ namespace MediaBrowser.Api.Playback
if (request.Width.HasValue || request.Height.HasValue || request.MaxHeight.HasValue || request.MaxWidth.HasValue)
{
outputSizeParam = GetOutputSizeParam(state, outputVideoCodec).TrimEnd('"');
- outputSizeParam = "," + outputSizeParam.Substring(outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase));
+
+ if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
+ {
+ outputSizeParam = "," + outputSizeParam.Substring(outputSizeParam.IndexOf("format", StringComparison.OrdinalIgnoreCase));
+ }
+ else
+ {
+ outputSizeParam = "," + outputSizeParam.Substring(outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase));
+ }
+ }
+
+ if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase) && outputSizeParam.Length == 0)
+ {
+ outputSizeParam = ",format=nv12|vaapi,hwupload";
}
var videoSizeParam = string.Empty;
@@ -802,10 +819,10 @@ namespace MediaBrowser.Api.Playback
{
if (state.PlayableStreamFileNames.Count > 0)
{
- return MediaEncoder.GetProbeSizeArgument(state.PlayableStreamFileNames.ToArray(), state.InputProtocol);
+ return MediaEncoder.GetProbeSizeAndAnalyzeDurationArgument(state.PlayableStreamFileNames.ToArray(), state.InputProtocol);
}
- return MediaEncoder.GetProbeSizeArgument(new[] { state.MediaPath }, state.InputProtocol);
+ return MediaEncoder.GetProbeSizeAndAnalyzeDurationArgument(new[] { state.MediaPath }, state.InputProtocol);
}
/// <summary>
@@ -1022,7 +1039,15 @@ namespace MediaBrowser.Api.Playback
var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
if (GetVideoEncoder(state).IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1)
{
- arg = "-hwaccel vaapi -hwaccel_output_format vaapi -vaapi_device " + encodingOptions.VaapiDevice + " " + arg;
+ var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.VideoRequest.SubtitleMethod == SubtitleDeliveryMethod.Encode;
+ var hwOutputFormat = "vaapi";
+
+ if (hasGraphicalSubs)
+ {
+ hwOutputFormat = "yuv420p";
+ }
+
+ arg = "-hwaccel vaapi -hwaccel_output_format " + hwOutputFormat + " -vaapi_device " + encodingOptions.VaapiDevice + " " + arg;
}
}
@@ -1219,7 +1244,7 @@ namespace MediaBrowser.Api.Playback
private void StartThrottler(StreamState state, TranscodingJob transcodingJob)
{
- if (EnableThrottling(state) && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
+ if (EnableThrottling(state))
{
transcodingJob.TranscodingThrottler = state.TranscodingThrottler = new TranscodingThrottler(transcodingJob, Logger, ServerConfigurationManager);
state.TranscodingThrottler.Start();
@@ -1832,26 +1857,37 @@ namespace MediaBrowser.Api.Playback
state.IsInputVideo = string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
- var archivable = item as IArchivable;
- state.IsInputArchive = archivable != null && archivable.IsArchive;
-
- MediaSourceInfo mediaSource;
+ MediaSourceInfo mediaSource = null;
if (string.IsNullOrWhiteSpace(request.LiveStreamId))
{
- var mediaSources = (await MediaSourceManager.GetPlayackMediaSources(request.Id, null, false, new[] { MediaType.Audio, MediaType.Video }, cancellationToken).ConfigureAwait(false)).ToList();
+ TranscodingJob currentJob = !string.IsNullOrWhiteSpace(request.PlaySessionId) ?
+ ApiEntryPoint.Instance.GetTranscodingJob(request.PlaySessionId)
+ : null;
- mediaSource = string.IsNullOrEmpty(request.MediaSourceId)
- ? mediaSources.First()
- : mediaSources.FirstOrDefault(i => string.Equals(i.Id, request.MediaSourceId));
+ if (currentJob != null)
+ {
+ mediaSource = currentJob.MediaSource;
+ }
- if (mediaSource == null && string.Equals(request.Id, request.MediaSourceId, StringComparison.OrdinalIgnoreCase))
+ if (mediaSource == null)
{
- mediaSource = mediaSources.First();
+ var mediaSources = (await MediaSourceManager.GetPlayackMediaSources(request.Id, null, false, new[] { MediaType.Audio, MediaType.Video }, cancellationToken).ConfigureAwait(false)).ToList();
+
+ mediaSource = string.IsNullOrEmpty(request.MediaSourceId)
+ ? mediaSources.First()
+ : mediaSources.FirstOrDefault(i => string.Equals(i.Id, request.MediaSourceId));
+
+ if (mediaSource == null && string.Equals(request.Id, request.MediaSourceId, StringComparison.OrdinalIgnoreCase))
+ {
+ mediaSource = mediaSources.First();
+ }
}
}
else
{
- mediaSource = await MediaSourceManager.GetLiveStream(request.LiveStreamId, cancellationToken).ConfigureAwait(false);
+ var liveStreamInfo = await MediaSourceManager.GetLiveStreamWithDirectStreamProvider(request.LiveStreamId, cancellationToken).ConfigureAwait(false);
+ mediaSource = liveStreamInfo.Item1;
+ state.DirectStreamProvider = liveStreamInfo.Item2;
}
var videoRequest = request as VideoStreamRequest;
@@ -2294,7 +2330,8 @@ namespace MediaBrowser.Api.Playback
state.TargetRefFrames,
state.TargetVideoStreamCount,
state.TargetAudioStreamCount,
- state.TargetVideoCodecTag);
+ state.TargetVideoCodecTag,
+ state.IsTargetAVC);
if (mediaProfile != null)
{
@@ -2429,7 +2466,8 @@ namespace MediaBrowser.Api.Playback
Url = "https://mb3admin.com/admin/service/transcoding/report",
CancellationToken = CancellationToken.None,
LogRequest = false,
- LogErrors = false
+ LogErrors = false,
+ BufferContent = false
};
options.RequestContent = JsonSerializer.SerializeToString(dict);
options.RequestContentType = "application/json";
@@ -2512,7 +2550,8 @@ namespace MediaBrowser.Api.Playback
state.TargetRefFrames,
state.TargetVideoStreamCount,
state.TargetAudioStreamCount,
- state.TargetVideoCodecTag
+ state.TargetVideoCodecTag,
+ state.IsTargetAVC
).FirstOrDefault() ?? string.Empty;
}
@@ -2567,6 +2606,7 @@ namespace MediaBrowser.Api.Playback
inputModifier += " " + GetFastSeekCommandLineParameter(state.Request);
inputModifier = inputModifier.Trim();
+ //inputModifier += " -fflags +genpts+ignidx+igndts";
if (state.VideoRequest != null && genPts)
{
inputModifier += " -fflags +genpts";
diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
index a0ac96b9d..319e4bbb6 100644
--- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
+++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
@@ -87,7 +87,8 @@ namespace MediaBrowser.Api.Playback.Hls
if (!FileSystem.FileExists(playlist))
{
- await ApiEntryPoint.Instance.TranscodingStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false);
+ var transcodingLock = ApiEntryPoint.Instance.GetTranscodingLock(playlist);
+ await transcodingLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false);
try
{
if (!FileSystem.FileExists(playlist))
@@ -104,13 +105,13 @@ namespace MediaBrowser.Api.Playback.Hls
throw;
}
- var waitForSegments = state.SegmentLength >= 10 ? 2 : 3;
+ var waitForSegments = state.SegmentLength >= 10 ? 2 : (state.SegmentLength > 3 || !isLive ? 3 : 3);
await WaitForMinimumSegmentCount(playlist, waitForSegments, cancellationTokenSource.Token).ConfigureAwait(false);
}
}
finally
{
- ApiEntryPoint.Instance.TranscodingStartLock.Release();
+ transcodingLock.Release();
}
}
@@ -128,10 +129,9 @@ namespace MediaBrowser.Api.Playback.Hls
var audioBitrate = state.OutputAudioBitrate ?? 0;
var videoBitrate = state.OutputVideoBitrate ?? 0;
- var appendBaselineStream = false;
var baselineStreamBitrate = 64000;
- var playlistText = GetMasterPlaylistFileText(playlist, videoBitrate + audioBitrate, appendBaselineStream, baselineStreamBitrate);
+ var playlistText = GetMasterPlaylistFileText(playlist, videoBitrate + audioBitrate, baselineStreamBitrate);
job = job ?? ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType);
@@ -151,9 +151,10 @@ namespace MediaBrowser.Api.Playback.Hls
{
var text = reader.ReadToEnd();
+ text = text.Replace("#EXTM3U", "#EXTM3U\n#EXT-X-PLAYLIST-TYPE:EVENT");
+
var newDuration = "#EXT-X-TARGETDURATION:" + segmentLength.ToString(UsCulture);
- // ffmpeg pads the reported length by a full second
text = text.Replace("#EXT-X-TARGETDURATION:" + (segmentLength + 1).ToString(UsCulture), newDuration, StringComparison.OrdinalIgnoreCase);
return text;
@@ -161,7 +162,7 @@ namespace MediaBrowser.Api.Playback.Hls
}
}
- private string GetMasterPlaylistFileText(string firstPlaylist, int bitrate, bool includeBaselineStream, int baselineStreamBitrate)
+ private string GetMasterPlaylistFileText(string firstPlaylist, int bitrate, int baselineStreamBitrate)
{
var builder = new StringBuilder();
@@ -175,14 +176,6 @@ namespace MediaBrowser.Api.Playback.Hls
var playlistUrl = "hls/" + Path.GetFileName(firstPlaylist).Replace(".m3u8", "/stream.m3u8");
builder.AppendLine(playlistUrl);
- // Low bitrate stream
- if (includeBaselineStream)
- {
- builder.AppendLine("#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" + baselineStreamBitrate.ToString(UsCulture));
- playlistUrl = "hls/" + Path.GetFileName(firstPlaylist).Replace(".m3u8", "-low/stream.m3u8");
- builder.AppendLine(playlistUrl);
- }
-
return builder.ToString();
}
@@ -190,32 +183,41 @@ namespace MediaBrowser.Api.Playback.Hls
{
Logger.Debug("Waiting for {0} segments in {1}", segmentCount, playlist);
- while (true)
+ while (!cancellationToken.IsCancellationRequested)
{
- // Need to use FileShare.ReadWrite because we're reading the file at the same time it's being written
- using (var fileStream = GetPlaylistFileStream(playlist))
+ try
{
- using (var reader = new StreamReader(fileStream))
+ // Need to use FileShare.ReadWrite because we're reading the file at the same time it's being written
+ using (var fileStream = GetPlaylistFileStream(playlist))
{
- var count = 0;
-
- while (!reader.EndOfStream)
+ using (var reader = new StreamReader(fileStream))
{
- var line = await reader.ReadLineAsync().ConfigureAwait(false);
+ var count = 0;
- if (line.IndexOf("#EXTINF:", StringComparison.OrdinalIgnoreCase) != -1)
+ while (!reader.EndOfStream)
{
- count++;
- if (count >= segmentCount)
+ var line = await reader.ReadLineAsync().ConfigureAwait(false);
+
+ if (line.IndexOf("#EXTINF:", StringComparison.OrdinalIgnoreCase) != -1)
{
- Logger.Debug("Finished waiting for {0} segments in {1}", segmentCount, playlist);
- return;
+ count++;
+ if (count >= segmentCount)
+ {
+ Logger.Debug("Finished waiting for {0} segments in {1}", segmentCount, playlist);
+ return;
+ }
}
}
+ await Task.Delay(100, cancellationToken).ConfigureAwait(false);
}
- await Task.Delay(100, cancellationToken).ConfigureAwait(false);
}
}
+ catch (IOException)
+ {
+ // May get an error if the file is locked
+ }
+
+ await Task.Delay(50, cancellationToken).ConfigureAwait(false);
}
}
diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
index 9cd55528d..51455c141 100644
--- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
+++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
@@ -171,14 +171,15 @@ namespace MediaBrowser.Api.Playback.Hls
return await GetSegmentResult(state, playlistPath, segmentPath, requestedIndex, job, cancellationToken).ConfigureAwait(false);
}
- await ApiEntryPoint.Instance.TranscodingStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false);
+ var transcodingLock = ApiEntryPoint.Instance.GetTranscodingLock(playlistPath);
+ await transcodingLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false);
var released = false;
try
{
if (FileSystem.FileExists(segmentPath))
{
job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
- ApiEntryPoint.Instance.TranscodingStartLock.Release();
+ transcodingLock.Release();
released = true;
return await GetSegmentResult(state, playlistPath, segmentPath, requestedIndex, job, cancellationToken).ConfigureAwait(false);
}
@@ -242,7 +243,7 @@ namespace MediaBrowser.Api.Playback.Hls
{
if (!released)
{
- ApiEntryPoint.Instance.TranscodingStartLock.Release();
+ transcodingLock.Release();
}
}
@@ -906,6 +907,7 @@ namespace MediaBrowser.Api.Playback.Hls
).Trim();
}
+ // TODO: check libavformat version for 57 50.100 and use -hls_flags split_by_time
return string.Format("{0}{11} {1} -map_metadata -1 -threads {2} {3} {4}{5} {6} -max_delay 5000000 -avoid_negative_ts disabled -start_at_zero -hls_time {7} -start_number {8} -hls_list_size {9} -y \"{10}\"",
inputModifier,
GetInputArgument(state),
diff --git a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs
index 27deaf25e..976fed3f0 100644
--- a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs
+++ b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs
@@ -68,8 +68,6 @@ namespace MediaBrowser.Api.Playback.Hls
[Api(Description = "Gets an Http live streaming segment file. Internal use only.")]
public class GetHlsVideoSegmentLegacy : VideoStreamRequest
{
- // TODO: Deprecate with new iOS app
-
public string PlaylistId { get; set; }
/// <summary>
@@ -113,7 +111,7 @@ namespace MediaBrowser.Api.Playback.Hls
var file = request.SegmentId + Path.GetExtension(Request.PathInfo);
file = Path.Combine(_config.ApplicationPaths.TranscodingTempPath, file);
- var normalizedPlaylistId = request.PlaylistId.Replace("-low", string.Empty);
+ var normalizedPlaylistId = request.PlaylistId;
var playlistPath = Directory.EnumerateFiles(_config.ApplicationPaths.TranscodingTempPath, "*")
.FirstOrDefault(i => string.Equals(Path.GetExtension(i), ".m3u8", StringComparison.OrdinalIgnoreCase) && i.IndexOf(normalizedPlaylistId, StringComparison.OrdinalIgnoreCase) != -1);
diff --git a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
index 8a14948d2..c7258d72f 100644
--- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
+++ b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
@@ -113,6 +113,8 @@ namespace MediaBrowser.Api.Playback.Hls
args += GetGraphicalSubtitleParam(state, codec);
}
+ args += " -flags -global_header";
+
return args;
}
diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs
index 656b2ee08..7fe7d5a21 100644
--- a/MediaBrowser.Api/Playback/MediaInfoService.cs
+++ b/MediaBrowser.Api/Playback/MediaInfoService.cs
@@ -107,7 +107,7 @@ namespace MediaBrowser.Api.Playback
{
var authInfo = AuthorizationContext.GetAuthorizationInfo(Request);
- var result = await _mediaSourceManager.OpenLiveStream(request, false, CancellationToken.None).ConfigureAwait(false);
+ var result = await _mediaSourceManager.OpenLiveStream(request, true, CancellationToken.None).ConfigureAwait(false);
var profile = request.DeviceProfile;
if (profile == null)
@@ -140,7 +140,7 @@ namespace MediaBrowser.Api.Playback
public void Post(CloseMediaSource request)
{
- var task = _mediaSourceManager.CloseLiveStream(request.LiveStreamId, CancellationToken.None);
+ var task = _mediaSourceManager.CloseLiveStream(request.LiveStreamId);
Task.WaitAll(task);
}
diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
index b8cb6b14f..809eabef8 100644
--- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
+++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
@@ -17,6 +17,7 @@ using System.IO;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
+using ServiceStack;
namespace MediaBrowser.Api.Playback.Progressive
{
@@ -26,12 +27,10 @@ namespace MediaBrowser.Api.Playback.Progressive
public abstract class BaseProgressiveStreamingService : BaseStreamingService
{
protected readonly IImageProcessor ImageProcessor;
- protected readonly IHttpClient HttpClient;
protected BaseProgressiveStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IJsonSerializer jsonSerializer, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, jsonSerializer)
{
ImageProcessor = imageProcessor;
- HttpClient = httpClient;
}
/// <summary>
@@ -122,6 +121,25 @@ namespace MediaBrowser.Api.Playback.Progressive
var responseHeaders = new Dictionary<string, string>();
+ if (request.Static && state.DirectStreamProvider != null)
+ {
+ AddDlnaHeaders(state, responseHeaders, true);
+
+ using (state)
+ {
+ var outputHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+
+ // TODO: Don't hardcode this
+ outputHeaders["Content-Type"] = MediaBrowser.Model.Net.MimeTypes.GetMimeType("file.ts");
+
+ var streamSource = new ProgressiveFileCopier(state.DirectStreamProvider, outputHeaders, null, Logger, CancellationToken.None)
+ {
+ AllowEndOfFile = false
+ };
+ return ResultFactory.GetAsyncStreamWriter(streamSource);
+ }
+ }
+
// Static remote stream
if (request.Static && state.InputProtocol == MediaProtocol.Http)
{
@@ -129,8 +147,7 @@ namespace MediaBrowser.Api.Playback.Progressive
using (state)
{
- return await GetStaticRemoteStreamResult(state, responseHeaders, isHeadRequest, cancellationTokenSource)
- .ConfigureAwait(false);
+ return await GetStaticRemoteStreamResult(state, responseHeaders, isHeadRequest, cancellationTokenSource).ConfigureAwait(false);
}
}
@@ -154,6 +171,19 @@ namespace MediaBrowser.Api.Playback.Progressive
using (state)
{
+ if (state.MediaSource.IsInfiniteStream)
+ {
+ var outputHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+
+ outputHeaders["Content-Type"] = contentType;
+
+ var streamSource = new ProgressiveFileCopier(FileSystem, state.MediaPath, outputHeaders, null, Logger, CancellationToken.None)
+ {
+ AllowEndOfFile = false
+ };
+ return ResultFactory.GetAsyncStreamWriter(streamSource);
+ }
+
TimeSpan? cacheDuration = null;
if (!string.IsNullOrEmpty(request.Tag))
@@ -345,7 +375,8 @@ namespace MediaBrowser.Api.Playback.Progressive
return streamResult;
}
- await ApiEntryPoint.Instance.TranscodingStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false);
+ var transcodingLock = ApiEntryPoint.Instance.GetTranscodingLock(outputPath);
+ await transcodingLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false);
try
{
TranscodingJob job;
@@ -376,7 +407,7 @@ namespace MediaBrowser.Api.Playback.Progressive
}
finally
{
- ApiEntryPoint.Instance.TranscodingStartLock.Release();
+ transcodingLock.Release();
}
}
diff --git a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs b/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs
index 0a9a44641..3477ad57b 100644
--- a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs
+++ b/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs
@@ -7,6 +7,7 @@ using CommonIO;
using MediaBrowser.Controller.Net;
using System.Collections.Generic;
using ServiceStack.Web;
+using MediaBrowser.Controller.Library;
namespace MediaBrowser.Api.Playback.Progressive
{
@@ -23,6 +24,10 @@ namespace MediaBrowser.Api.Playback.Progressive
private const int BufferSize = 81920;
private long _bytesWritten = 0;
+ public long StartPosition { get; set; }
+ public bool AllowEndOfFile = true;
+
+ private IDirectStreamProvider _directStreamProvider;
public ProgressiveFileCopier(IFileSystem fileSystem, string path, Dictionary<string, string> outputHeaders, TranscodingJob job, ILogger logger, CancellationToken cancellationToken)
{
@@ -34,6 +39,15 @@ namespace MediaBrowser.Api.Playback.Progressive
_cancellationToken = cancellationToken;
}
+ public ProgressiveFileCopier(IDirectStreamProvider directStreamProvider, Dictionary<string, string> outputHeaders, TranscodingJob job, ILogger logger, CancellationToken cancellationToken)
+ {
+ _directStreamProvider = directStreamProvider;
+ _outputHeaders = outputHeaders;
+ _job = job;
+ _logger = logger;
+ _cancellationToken = cancellationToken;
+ }
+
public IDictionary<string, string> Options
{
get
@@ -42,17 +56,33 @@ namespace MediaBrowser.Api.Playback.Progressive
}
}
+ private Stream GetInputStream()
+ {
+ return _fileSystem.GetFileStream(_path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true);
+ }
+
public async Task WriteToAsync(Stream outputStream)
{
try
{
+ if (_directStreamProvider != null)
+ {
+ await _directStreamProvider.CopyToAsync(outputStream, _cancellationToken).ConfigureAwait(false);
+ return;
+ }
+
var eofCount = 0;
- using (var fs = _fileSystem.GetFileStream(_path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true))
+ using (var inputStream = GetInputStream())
{
- while (eofCount < 15)
+ if (StartPosition > 0)
+ {
+ inputStream.Position = StartPosition;
+ }
+
+ while (eofCount < 15 || !AllowEndOfFile)
{
- var bytesRead = await CopyToAsyncInternal(fs, outputStream, BufferSize, _cancellationToken).ConfigureAwait(false);
+ var bytesRead = await CopyToAsyncInternal(inputStream, outputStream, BufferSize, _cancellationToken).ConfigureAwait(false);
//var position = fs.Position;
//_logger.Debug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path);
diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs
index 2e92c4a49..003599390 100644
--- a/MediaBrowser.Api/Playback/StreamState.cs
+++ b/MediaBrowser.Api/Playback/StreamState.cs
@@ -37,6 +37,7 @@ namespace MediaBrowser.Api.Playback
/// </summary>
/// <value>The log file stream.</value>
public Stream LogFileStream { get; set; }
+ public IDirectStreamProvider DirectStreamProvider { get; set; }
public string InputContainer { get; set; }
@@ -62,7 +63,6 @@ namespace MediaBrowser.Api.Playback
get { return Request is VideoStreamRequest; }
}
public bool IsInputVideo { get; set; }
- public bool IsInputArchive { get; set; }
public VideoType VideoType { get; set; }
public IsoType? IsoType { get; set; }
@@ -88,9 +88,17 @@ namespace MediaBrowser.Api.Playback
return 10;
}
+ if (!RunTimeTicks.HasValue)
+ {
+ return 3;
+ }
return 6;
}
+ if (!RunTimeTicks.HasValue)
+ {
+ return 3;
+ }
return 3;
}
}
@@ -217,7 +225,7 @@ namespace MediaBrowser.Api.Playback
{
try
{
- await _mediaSourceManager.CloseLiveStream(MediaSource.LiveStreamId, CancellationToken.None).ConfigureAwait(false);
+ await _mediaSourceManager.CloseLiveStream(MediaSource.LiveStreamId).ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -508,5 +516,18 @@ namespace MediaBrowser.Api.Playback
return false;
}
}
+
+ public bool? IsTargetAVC
+ {
+ get
+ {
+ if (Request.Static)
+ {
+ return VideoStream == null ? null : VideoStream.IsAVC;
+ }
+
+ return true;
+ }
+ }
}
}
diff --git a/MediaBrowser.Api/Reports/Data/ReportBuilder.cs b/MediaBrowser.Api/Reports/Data/ReportBuilder.cs
index e4a560383..c9c63847c 100644
--- a/MediaBrowser.Api/Reports/Data/ReportBuilder.cs
+++ b/MediaBrowser.Api/Reports/Data/ReportBuilder.cs
@@ -517,10 +517,6 @@ namespace MediaBrowser.Api.Reports
internalHeader = HeaderMetadata.Album;
break;
- case HeaderMetadata.Countries:
- option.Column = (i, r) => this.GetListAsString(this.GetObject<IHasProductionLocations, List<string>>(i, (x) => x.ProductionLocations));
- break;
-
case HeaderMetadata.Disc:
option.Column = (i, r) => i.ParentIndexNumber;
break;
diff --git a/MediaBrowser.Api/Reports/Stat/ReportStatBuilder.cs b/MediaBrowser.Api/Reports/Stat/ReportStatBuilder.cs
index 0da4857ac..52b095dee 100644
--- a/MediaBrowser.Api/Reports/Stat/ReportStatBuilder.cs
+++ b/MediaBrowser.Api/Reports/Stat/ReportStatBuilder.cs
@@ -36,7 +36,6 @@ namespace MediaBrowser.Api.Reports
result = this.GetResultStudios(result, items, topItem);
result = this.GetResultPersons(result, items, topItem);
result = this.GetResultProductionYears(result, items, topItem);
- result = this.GetResulProductionLocations(result, items, topItem);
result = this.GetResultCommunityRatings(result, items, topItem);
result = this.GetResultParentalRatings(result, items, topItem);
@@ -100,30 +99,6 @@ namespace MediaBrowser.Api.Reports
}
}
- /// <summary> Gets resul production locations. </summary>
- /// <param name="result"> The result. </param>
- /// <param name="items"> The items. </param>
- /// <param name="topItem"> The top item. </param>
- /// <returns> The resul production locations. </returns>
- private ReportStatResult GetResulProductionLocations(ReportStatResult result, BaseItem[] items, int topItem = 5)
- {
- this.GetGroups(result, GetLocalizedHeader(HeaderMetadata.Countries), topItem,
- items.OfType<IHasProductionLocations>()
- .Where(x => x.ProductionLocations != null)
- .SelectMany(x => x.ProductionLocations)
- .GroupBy(x => x)
- .OrderByDescending(x => x.Count())
- .Take(topItem)
- .Select(x => new ReportStatItem
- {
- Name = x.Key.ToString(),
- Value = x.Count().ToString()
- })
- );
-
- return result;
- }
-
/// <summary> Gets result community ratings. </summary>
/// <param name="result"> The result. </param>
/// <param name="items"> The items. </param>
diff --git a/MediaBrowser.Api/SimilarItemsHelper.cs b/MediaBrowser.Api/SimilarItemsHelper.cs
index 1621c8056..ddcb6b7bf 100644
--- a/MediaBrowser.Api/SimilarItemsHelper.cs
+++ b/MediaBrowser.Api/SimilarItemsHelper.cs
@@ -81,7 +81,8 @@ namespace MediaBrowser.Api
var query = new InternalItemsQuery(user)
{
IncludeItemTypes = includeTypes.Select(i => i.Name).ToArray(),
- Recursive = true
+ Recursive = true,
+ DtoOptions = dtoOptions
};
// ExcludeArtistIds
diff --git a/MediaBrowser.Api/StartupWizardService.cs b/MediaBrowser.Api/StartupWizardService.cs
index 176b497d7..d5158677c 100644
--- a/MediaBrowser.Api/StartupWizardService.cs
+++ b/MediaBrowser.Api/StartupWizardService.cs
@@ -88,8 +88,6 @@ namespace MediaBrowser.Api
var result = new StartupConfiguration
{
UICulture = _config.Configuration.UICulture,
- EnableInternetProviders = _config.Configuration.EnableInternetProviders,
- SaveLocalMeta = _config.Configuration.SaveLocalMeta,
MetadataCountryCode = _config.Configuration.MetadataCountryCode,
PreferredMetadataLanguage = _config.Configuration.PreferredMetadataLanguage
};
@@ -116,15 +114,16 @@ namespace MediaBrowser.Api
config.EnableLocalizedGuids = true;
config.EnableStandaloneMusicKeys = true;
config.EnableCaseSensitiveItemIds = true;
- //config.EnableFolderView = true;
+ config.EnableFolderView = true;
config.SchemaVersion = 109;
+ config.EnableSimpleArtistDetection = true;
+ config.SkipDeserializationForBasicTypes = true;
+ config.SkipDeserializationForPrograms = true;
}
public void Post(UpdateStartupConfiguration request)
{
_config.Configuration.UICulture = request.UICulture;
- _config.Configuration.EnableInternetProviders = request.EnableInternetProviders;
- _config.Configuration.SaveLocalMeta = request.SaveLocalMeta;
_config.Configuration.MetadataCountryCode = request.MetadataCountryCode;
_config.Configuration.PreferredMetadataLanguage = request.PreferredMetadataLanguage;
_config.SaveConfiguration();
@@ -215,8 +214,6 @@ namespace MediaBrowser.Api
public class StartupConfiguration
{
public string UICulture { get; set; }
- public bool EnableInternetProviders { get; set; }
- public bool SaveLocalMeta { get; set; }
public string MetadataCountryCode { get; set; }
public string PreferredMetadataLanguage { get; set; }
public string LiveTvTunerType { get; set; }
diff --git a/MediaBrowser.Api/Subtitles/SubtitleService.cs b/MediaBrowser.Api/Subtitles/SubtitleService.cs
index fe13e8b21..b07a31a87 100644
--- a/MediaBrowser.Api/Subtitles/SubtitleService.cs
+++ b/MediaBrowser.Api/Subtitles/SubtitleService.cs
@@ -148,7 +148,7 @@ namespace MediaBrowser.Api.Subtitles
{
var item = (Video)_libraryManager.GetItemById(new Guid(request.Id));
- var mediaSource = await _mediaSourceManager.GetMediaSource(item, request.MediaSourceId, false).ConfigureAwait(false);
+ var mediaSource = await _mediaSourceManager.GetMediaSource(item, request.MediaSourceId, null, false, CancellationToken.None).ConfigureAwait(false);
var builder = new StringBuilder();
diff --git a/MediaBrowser.Api/TvShowsService.cs b/MediaBrowser.Api/TvShowsService.cs
index a0d69317c..ef93d2fc9 100644
--- a/MediaBrowser.Api/TvShowsService.cs
+++ b/MediaBrowser.Api/TvShowsService.cs
@@ -302,6 +302,8 @@ namespace MediaBrowser.Api
(!string.IsNullOrWhiteSpace(request.UserId) ? user.RootFolder :
_libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id);
+ var dtoOptions = GetDtoOptions(request);
+
var itemsResult = _libraryManager.GetItemList(new InternalItemsQuery(user)
{
Limit = request.Limit,
@@ -309,12 +311,11 @@ namespace MediaBrowser.Api
{
typeof(Series).Name
},
- SimilarTo = item
+ SimilarTo = item,
+ DtoOptions = dtoOptions
}).ToList();
- var dtoOptions = GetDtoOptions(request);
-
var result = new QueryResult<BaseItemDto>
{
Items = (await _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user).ConfigureAwait(false)).ToArray(),
@@ -333,6 +334,8 @@ namespace MediaBrowser.Api
var parentIdGuid = string.IsNullOrWhiteSpace(request.ParentId) ? (Guid?)null : new Guid(request.ParentId);
+ var options = GetDtoOptions(request);
+
var itemsResult = _libraryManager.GetItemList(new InternalItemsQuery(user)
{
IncludeItemTypes = new[] { typeof(Episode).Name },
@@ -342,12 +345,11 @@ namespace MediaBrowser.Api
StartIndex = request.StartIndex,
Limit = request.Limit,
ParentId = parentIdGuid,
- Recursive = true
+ Recursive = true,
+ DtoOptions = options
}).ToList();
- var options = GetDtoOptions(request);
-
var returnItems = (await _dtoService.GetBaseItemDtos(itemsResult, options, user).ConfigureAwait(false)).ToArray();
var result = new ItemsResult
diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs
index 5381f9004..182a92fc8 100644
--- a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs
+++ b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs
@@ -205,6 +205,7 @@ namespace MediaBrowser.Api.UserLibrary
private void SetItemCounts(BaseItemDto dto, ItemCounts counts)
{
dto.ChildCount = counts.ItemCount;
+ dto.ProgramCount = counts.ProgramCount;
dto.SeriesCount = counts.SeriesCount;
dto.EpisodeCount = counts.EpisodeCount;
dto.MovieCount = counts.MovieCount;
diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs
index 681624ba2..04395da3c 100644
--- a/MediaBrowser.Api/UserLibrary/ItemsService.cs
+++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs
@@ -99,8 +99,10 @@ namespace MediaBrowser.Api.UserLibrary
private async Task<ItemsResult> GetItems(GetItems request)
{
var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
-
- var result = await GetQueryResult(request, user).ConfigureAwait(false);
+
+ var dtoOptions = GetDtoOptions(request);
+
+ var result = await GetQueryResult(request, dtoOptions, user).ConfigureAwait(false);
if (result == null)
{
@@ -112,8 +114,6 @@ namespace MediaBrowser.Api.UserLibrary
throw new InvalidOperationException("GetItemsToSerialize result.Items returned null");
}
- var dtoOptions = GetDtoOptions(request);
-
var dtoList = await _dtoService.GetBaseItemDtos(result.Items, dtoOptions, user).ConfigureAwait(false);
if (dtoList == null)
@@ -131,24 +131,31 @@ namespace MediaBrowser.Api.UserLibrary
/// <summary>
/// Gets the items to serialize.
/// </summary>
- /// <param name="request">The request.</param>
- /// <param name="user">The user.</param>
- /// <returns>IEnumerable{BaseItem}.</returns>
- private async Task<QueryResult<BaseItem>> GetQueryResult(GetItems request, User user)
+ private async Task<QueryResult<BaseItem>> GetQueryResult(GetItems request, DtoOptions dtoOptions, User user)
{
var item = string.IsNullOrEmpty(request.ParentId) ?
- user == null ? _libraryManager.RootFolder : user.RootFolder :
+ null :
_libraryManager.GetItemById(request.ParentId);
if (string.Equals(request.IncludeItemTypes, "Playlist", StringComparison.OrdinalIgnoreCase))
{
- //item = user == null ? _libraryManager.RootFolder : user.RootFolder;
+ if (item == null || user != null)
+ {
+ item = _libraryManager.RootFolder.Children.OfType<Folder>().FirstOrDefault(i => string.Equals(i.GetType().Name, "PlaylistsFolder", StringComparison.OrdinalIgnoreCase));
+ }
}
else if (string.Equals(request.IncludeItemTypes, "BoxSet", StringComparison.OrdinalIgnoreCase))
{
item = user == null ? _libraryManager.RootFolder : user.RootFolder;
}
+ if (item == null)
+ {
+ item = string.IsNullOrEmpty(request.ParentId) ?
+ user == null ? _libraryManager.RootFolder : user.RootFolder :
+ _libraryManager.GetItemById(request.ParentId);
+ }
+
// Default list type = children
var folder = item as Folder;
@@ -159,14 +166,14 @@ namespace MediaBrowser.Api.UserLibrary
if (request.Recursive || !string.IsNullOrEmpty(request.Ids) || user == null)
{
- return await folder.GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
+ return await folder.GetItems(GetItemsQuery(request, dtoOptions, user)).ConfigureAwait(false);
}
var userRoot = item as UserRootFolder;
if (userRoot == null)
{
- return await folder.GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
+ return await folder.GetItems(GetItemsQuery(request, dtoOptions, user)).ConfigureAwait(false);
}
IEnumerable<BaseItem> items = folder.GetChildren(user, true);
@@ -180,7 +187,7 @@ namespace MediaBrowser.Api.UserLibrary
};
}
- private InternalItemsQuery GetItemsQuery(GetItems request, User user)
+ private InternalItemsQuery GetItemsQuery(GetItems request, DtoOptions dtoOptions, User user)
{
var query = new InternalItemsQuery(user)
{
@@ -241,7 +248,8 @@ namespace MediaBrowser.Api.UserLibrary
AiredDuringSeason = request.AiredDuringSeason,
AlbumArtistStartsWithOrGreater = request.AlbumArtistStartsWithOrGreater,
EnableTotalRecordCount = request.EnableTotalRecordCount,
- ExcludeItemIds = request.GetExcludeItemIds()
+ ExcludeItemIds = request.GetExcludeItemIds(),
+ DtoOptions = dtoOptions
};
if (!string.IsNullOrWhiteSpace(request.Ids))
diff --git a/MediaBrowser.Common.Implementations/BaseApplicationHost.cs b/MediaBrowser.Common.Implementations/BaseApplicationHost.cs
index baf5afc1b..4099c9c56 100644
--- a/MediaBrowser.Common.Implementations/BaseApplicationHost.cs
+++ b/MediaBrowser.Common.Implementations/BaseApplicationHost.cs
@@ -30,6 +30,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
+using MediaBrowser.Common.IO;
namespace MediaBrowser.Common.Implementations
{
@@ -192,6 +193,8 @@ namespace MediaBrowser.Common.Implementations
get { return Environment.OSVersion.VersionString; }
}
+ public IMemoryStreamProvider MemoryStreamProvider { get; set; }
+
/// <summary>
/// Initializes a new instance of the <see cref="BaseApplicationHost{TApplicationPathsType}"/> class.
/// </summary>
@@ -231,6 +234,8 @@ namespace MediaBrowser.Common.Implementations
JsonSerializer = CreateJsonSerializer();
+ MemoryStreamProvider = new MemoryStreamProvider();
+
OnLoggerLoaded(true);
LogManager.LoggerLoaded += (s, e) => OnLoggerLoaded(false);
@@ -456,6 +461,7 @@ namespace MediaBrowser.Common.Implementations
RegisterSingleInstance(JsonSerializer);
RegisterSingleInstance(XmlSerializer);
+ RegisterSingleInstance(MemoryStreamProvider);
RegisterSingleInstance(LogManager);
RegisterSingleInstance(Logger);
@@ -464,7 +470,7 @@ namespace MediaBrowser.Common.Implementations
RegisterSingleInstance(FileSystemManager);
- HttpClient = new HttpClientManager.HttpClientManager(ApplicationPaths, LogManager.GetLogger("HttpClient"), FileSystemManager);
+ HttpClient = new HttpClientManager.HttpClientManager(ApplicationPaths, LogManager.GetLogger("HttpClient"), FileSystemManager, MemoryStreamProvider);
RegisterSingleInstance(HttpClient);
NetworkManager = CreateNetworkManager(LogManager.GetLogger("NetworkManager"));
diff --git a/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs b/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs
index 371757f6c..eec18e985 100644
--- a/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs
+++ b/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs
@@ -42,6 +42,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
private readonly IApplicationPaths _appPaths;
private readonly IFileSystem _fileSystem;
+ private readonly IMemoryStreamProvider _memoryStreamProvider;
/// <summary>
/// Initializes a new instance of the <see cref="HttpClientManager" /> class.
@@ -52,7 +53,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
/// <exception cref="System.ArgumentNullException">appPaths
/// or
/// logger</exception>
- public HttpClientManager(IApplicationPaths appPaths, ILogger logger, IFileSystem fileSystem)
+ public HttpClientManager(IApplicationPaths appPaths, ILogger logger, IFileSystem fileSystem, IMemoryStreamProvider memoryStreamProvider)
{
if (appPaths == null)
{
@@ -65,6 +66,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
_logger = logger;
_fileSystem = fileSystem;
+ _memoryStreamProvider = memoryStreamProvider;
_appPaths = appPaths;
// http://stackoverflow.com/questions/566437/http-post-returns-the-error-417-expectation-failed-c
@@ -269,6 +271,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
Url = url,
ResourcePool = resourcePool,
CancellationToken = cancellationToken,
+ BufferContent = resourcePool != null
});
}
@@ -293,12 +296,9 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
/// </exception>
public async Task<HttpResponseInfo> SendAsync(HttpRequestOptions options, string httpMethod)
{
- HttpResponseInfo response;
-
if (options.CacheMode == CacheMode.None)
{
- response = await SendAsyncInternal(options, httpMethod).ConfigureAwait(false);
- return response;
+ return await SendAsyncInternal(options, httpMethod).ConfigureAwait(false);
}
var url = options.Url;
@@ -306,7 +306,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
var responseCachePath = Path.Combine(_appPaths.CachePath, "httpclient", urlHash);
- response = await GetCachedResponse(responseCachePath, options.CacheLength, url).ConfigureAwait(false);
+ var response = await GetCachedResponse(responseCachePath, options.CacheLength, url).ConfigureAwait(false);
if (response != null)
{
return response;
@@ -332,7 +332,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
{
using (var stream = _fileSystem.GetFileStream(responseCachePath, FileMode.Open, FileAccess.Read, FileShare.Read, true))
{
- var memoryStream = new MemoryStream();
+ var memoryStream = _memoryStreamProvider.CreateNew();
await stream.CopyToAsync(memoryStream).ConfigureAwait(false);
memoryStream.Position = 0;
@@ -366,7 +366,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
using (var responseStream = response.Content)
{
- var memoryStream = new MemoryStream();
+ var memoryStream = _memoryStreamProvider.CreateNew();
await responseStream.CopyToAsync(memoryStream).ConfigureAwait(false);
memoryStream.Position = 0;
@@ -458,7 +458,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
using (var stream = httpResponse.GetResponseStream())
{
- var memoryStream = new MemoryStream();
+ var memoryStream = _memoryStreamProvider.CreateNew();
await stream.CopyToAsync(memoryStream).ConfigureAwait(false);
@@ -553,7 +553,8 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
{
Url = url,
ResourcePool = resourcePool,
- CancellationToken = cancellationToken
+ CancellationToken = cancellationToken,
+ BufferContent = resourcePool != null
}, postData);
}
@@ -563,7 +564,6 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
/// </summary>
/// <param name="options">The options.</param>
/// <returns>Task{System.String}.</returns>
- /// <exception cref="System.ArgumentNullException">progress</exception>
public async Task<string> GetTempFile(HttpRequestOptions options)
{
var response = await GetTempFileResponse(options).ConfigureAwait(false);
diff --git a/MediaBrowser.Common.Implementations/IO/MemoryStreamProvider.cs b/MediaBrowser.Common.Implementations/IO/MemoryStreamProvider.cs
new file mode 100644
index 000000000..364055283
--- /dev/null
+++ b/MediaBrowser.Common.Implementations/IO/MemoryStreamProvider.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using MediaBrowser.Common.IO;
+using Microsoft.IO;
+
+namespace MediaBrowser.Common.Implementations.IO
+{
+ public class MemoryStreamProvider : IMemoryStreamProvider
+ {
+ readonly RecyclableMemoryStreamManager _manager = new RecyclableMemoryStreamManager();
+
+ public MemoryStream CreateNew()
+ {
+ return _manager.GetStream();
+ }
+
+ public MemoryStream CreateNew(int capacity)
+ {
+ return _manager.GetStream("RecyclableMemoryStream", capacity);
+ }
+
+ public MemoryStream CreateNew(byte[] buffer)
+ {
+ return _manager.GetStream("RecyclableMemoryStream", buffer, 0, buffer.Length);
+ }
+ }
+}
diff --git a/MediaBrowser.Common.Implementations/Logging/NLogger.cs b/MediaBrowser.Common.Implementations/Logging/NLogger.cs
index 29b618890..97bc437a0 100644
--- a/MediaBrowser.Common.Implementations/Logging/NLogger.cs
+++ b/MediaBrowser.Common.Implementations/Logging/NLogger.cs
@@ -72,6 +72,11 @@ namespace MediaBrowser.Common.Implementations.Logging
/// <param name="paramList">The param list.</param>
public void Debug(string message, params object[] paramList)
{
+ if (_logManager.LogSeverity == LogSeverity.Info)
+ {
+ return;
+ }
+
_logger.Debug(message, paramList);
}
@@ -137,6 +142,11 @@ namespace MediaBrowser.Common.Implementations.Logging
/// <param name="additionalContent">Content of the additional.</param>
public void LogMultiline(string message, LogSeverity severity, StringBuilder additionalContent)
{
+ if (severity == LogSeverity.Debug && _logManager.LogSeverity == LogSeverity.Info)
+ {
+ return;
+ }
+
additionalContent.Insert(0, message + Environment.NewLine);
const char tabChar = '\t';
diff --git a/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj b/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj
index ced2dd5a3..f3444f01b 100644
--- a/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj
+++ b/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@@ -14,6 +14,7 @@
<ProductVersion>10.0.0</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ <TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@@ -23,7 +24,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
- <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ <TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>none</DebugType>
@@ -51,11 +52,15 @@
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\CommonIO.1.0.0.9\lib\net45\CommonIO.dll</HintPath>
</Reference>
+ <Reference Include="Microsoft.IO.RecyclableMemoryStream, Version=1.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+ <HintPath>..\packages\Microsoft.IO.RecyclableMemoryStream.1.1.0.0\lib\net45\Microsoft.IO.RecyclableMemoryStream.dll</HintPath>
+ <Private>True</Private>
+ </Reference>
<Reference Include="MoreLinq">
<HintPath>..\packages\morelinq.1.4.0\lib\net35\MoreLinq.dll</HintPath>
</Reference>
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
- <HintPath>..\packages\NLog.4.3.6\lib\net45\NLog.dll</HintPath>
+ <HintPath>..\packages\NLog.4.3.8\lib\net45\NLog.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Patterns.Logging">
@@ -65,8 +70,8 @@
<SpecificVersion>False</SpecificVersion>
<HintPath>..\ThirdParty\SharpCompress\SharpCompress.dll</HintPath>
</Reference>
- <Reference Include="SimpleInjector, Version=3.2.0.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL">
- <HintPath>..\packages\SimpleInjector.3.2.0\lib\net45\SimpleInjector.dll</HintPath>
+ <Reference Include="SimpleInjector, Version=3.2.2.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL">
+ <HintPath>..\packages\SimpleInjector.3.2.2\lib\net45\SimpleInjector.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
@@ -75,6 +80,9 @@
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net" />
+ <Reference Include="System.Text.Json">
+ <HintPath>..\ThirdParty\fastjsonparser\System.Text.Json.dll</HintPath>
+ </Reference>
<Reference Include="System.Xml" />
<Reference Include="ServiceStack.Text">
<HintPath>..\ThirdParty\ServiceStack.Text\ServiceStack.Text.dll</HintPath>
@@ -93,6 +101,7 @@
<Compile Include="HttpClientManager\HttpClientInfo.cs" />
<Compile Include="HttpClientManager\HttpClientManager.cs" />
<Compile Include="IO\IsoManager.cs" />
+ <Compile Include="IO\MemoryStreamProvider.cs" />
<Compile Include="Logging\LogHelper.cs" />
<Compile Include="Logging\NLogger.cs" />
<Compile Include="Logging\NlogManager.cs" />
diff --git a/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
index e6b6bc2e3..ced85780f 100644
--- a/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
+++ b/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
@@ -390,13 +390,13 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
try
{
- var localTask = ScheduledTask.Execute(CurrentCancellationTokenSource.Token, progress);
-
if (options != null && options.MaxRuntimeMs.HasValue)
{
CurrentCancellationTokenSource.CancelAfter(options.MaxRuntimeMs.Value);
}
+ var localTask = ScheduledTask.Execute(CurrentCancellationTokenSource.Token, progress);
+
await localTask.ConfigureAwait(false);
status = TaskCompletionStatus.Completed;
diff --git a/MediaBrowser.Common.Implementations/Security/PluginSecurityManager.cs b/MediaBrowser.Common.Implementations/Security/PluginSecurityManager.cs
index 10c0f8fc9..5d440609e 100644
--- a/MediaBrowser.Common.Implementations/Security/PluginSecurityManager.cs
+++ b/MediaBrowser.Common.Implementations/Security/PluginSecurityManager.cs
@@ -169,7 +169,8 @@ namespace MediaBrowser.Common.Implementations.Security
var options = new HttpRequestOptions()
{
Url = AppstoreRegUrl,
- CancellationToken = CancellationToken.None
+ CancellationToken = CancellationToken.None,
+ BufferContent = false
};
options.RequestHeaders.Add("X-Emby-Token", _appHost.SystemId);
options.RequestContent = parameters;
@@ -269,7 +270,8 @@ namespace MediaBrowser.Common.Implementations.Security
Url = MBValidateUrl,
// Seeing block length errors
- EnableHttpCompression = false
+ EnableHttpCompression = false,
+ BufferContent = false
};
options.SetPostData(data);
diff --git a/MediaBrowser.Common.Implementations/Updates/GithubUpdater.cs b/MediaBrowser.Common.Implementations/Updates/GithubUpdater.cs
index 84c08439e..371c2ea11 100644
--- a/MediaBrowser.Common.Implementations/Updates/GithubUpdater.cs
+++ b/MediaBrowser.Common.Implementations/Updates/GithubUpdater.cs
@@ -30,7 +30,8 @@ namespace MediaBrowser.Common.Implementations.Updates
Url = url,
EnableKeepAlive = false,
CancellationToken = cancellationToken,
- UserAgent = "Emby/3.0"
+ UserAgent = "Emby/3.0",
+ BufferContent = false
};
if (cacheLength.Ticks > 0)
@@ -105,7 +106,8 @@ namespace MediaBrowser.Common.Implementations.Updates
Url = url,
EnableKeepAlive = false,
CancellationToken = cancellationToken,
- UserAgent = "Emby/3.0"
+ UserAgent = "Emby/3.0",
+ BufferContent = false
};
using (var stream = await _httpClient.Get(options).ConfigureAwait(false))
diff --git a/MediaBrowser.Common.Implementations/Updates/InstallationManager.cs b/MediaBrowser.Common.Implementations/Updates/InstallationManager.cs
index 8c7646209..9674199fe 100644
--- a/MediaBrowser.Common.Implementations/Updates/InstallationManager.cs
+++ b/MediaBrowser.Common.Implementations/Updates/InstallationManager.cs
@@ -148,14 +148,10 @@ namespace MediaBrowser.Common.Implementations.Updates
/// <summary>
/// Gets all available packages.
/// </summary>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <param name="withRegistration">if set to <c>true</c> [with registration].</param>
- /// <param name="packageType">Type of the package.</param>
- /// <param name="applicationVersion">The application version.</param>
/// <returns>Task{List{PackageInfo}}.</returns>
public async Task<IEnumerable<PackageInfo>> GetAvailablePackages(CancellationToken cancellationToken,
bool withRegistration = true,
- PackageType? packageType = null,
+ string packageType = null,
Version applicationVersion = null)
{
var data = new Dictionary<string, string>
@@ -293,7 +289,7 @@ namespace MediaBrowser.Common.Implementations.Updates
return packages;
}
- protected IEnumerable<PackageInfo> FilterPackages(List<PackageInfo> packages, PackageType? packageType, Version applicationVersion)
+ protected IEnumerable<PackageInfo> FilterPackages(List<PackageInfo> packages, string packageType, Version applicationVersion)
{
foreach (var package in packages)
{
@@ -301,9 +297,9 @@ namespace MediaBrowser.Common.Implementations.Updates
.OrderByDescending(GetPackageVersion).ToList();
}
- if (packageType.HasValue)
+ if (!string.IsNullOrWhiteSpace(packageType))
{
- packages = packages.Where(p => p.type == packageType.Value).ToList();
+ packages = packages.Where(p => string.Equals(p.type, packageType, StringComparison.OrdinalIgnoreCase)).ToList();
}
// If an app version was supplied, filter the versions for each package to only include supported versions
diff --git a/MediaBrowser.Common.Implementations/packages.config b/MediaBrowser.Common.Implementations/packages.config
index 594b4c7c5..40c727a06 100644
--- a/MediaBrowser.Common.Implementations/packages.config
+++ b/MediaBrowser.Common.Implementations/packages.config
@@ -1,8 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="CommonIO" version="1.0.0.9" targetFramework="net45" />
+ <package id="Microsoft.IO.RecyclableMemoryStream" version="1.1.0.0" targetFramework="net45" />
<package id="morelinq" version="1.4.0" targetFramework="net45" />
- <package id="NLog" version="4.3.6" targetFramework="net45" />
+ <package id="NLog" version="4.3.8" targetFramework="net45" />
<package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" />
- <package id="SimpleInjector" version="3.2.0" targetFramework="net45" />
+ <package id="SimpleInjector" version="3.2.2" targetFramework="net45" />
</packages> \ No newline at end of file
diff --git a/MediaBrowser.Common/IO/IMemoryStreamProvider.cs b/MediaBrowser.Common/IO/IMemoryStreamProvider.cs
new file mode 100644
index 000000000..1c0e98018
--- /dev/null
+++ b/MediaBrowser.Common/IO/IMemoryStreamProvider.cs
@@ -0,0 +1,11 @@
+using System.IO;
+
+namespace MediaBrowser.Common.IO
+{
+ public interface IMemoryStreamProvider
+ {
+ MemoryStream CreateNew();
+ MemoryStream CreateNew(int capacity);
+ MemoryStream CreateNew(byte[] buffer);
+ }
+}
diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj
index 17f211d84..a46aaf9f7 100644
--- a/MediaBrowser.Common/MediaBrowser.Common.csproj
+++ b/MediaBrowser.Common/MediaBrowser.Common.csproj
@@ -60,6 +60,7 @@
<Compile Include="Extensions\BaseExtensions.cs" />
<Compile Include="Extensions\ResourceNotFoundException.cs" />
<Compile Include="IDependencyContainer.cs" />
+ <Compile Include="IO\IMemoryStreamProvider.cs" />
<Compile Include="IO\ProgressStream.cs" />
<Compile Include="IO\StreamDefaults.cs" />
<Compile Include="Configuration\IApplicationPaths.cs" />
diff --git a/MediaBrowser.Common/Updates/IInstallationManager.cs b/MediaBrowser.Common/Updates/IInstallationManager.cs
index 68853f05e..f7a202f97 100644
--- a/MediaBrowser.Common/Updates/IInstallationManager.cs
+++ b/MediaBrowser.Common/Updates/IInstallationManager.cs
@@ -51,7 +51,7 @@ namespace MediaBrowser.Common.Updates
/// <returns>Task{List{PackageInfo}}.</returns>
Task<IEnumerable<PackageInfo>> GetAvailablePackages(CancellationToken cancellationToken,
bool withRegistration = true,
- PackageType? packageType = null,
+ string packageType = null,
Version applicationVersion = null);
/// <summary>
diff --git a/MediaBrowser.Controller/Channels/IChannelManager.cs b/MediaBrowser.Controller/Channels/IChannelManager.cs
index 3c46247a7..9177e2d81 100644
--- a/MediaBrowser.Controller/Channels/IChannelManager.cs
+++ b/MediaBrowser.Controller/Channels/IChannelManager.cs
@@ -134,15 +134,5 @@ namespace MediaBrowser.Controller.Channels
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>BaseItemDto.</returns>
Task<BaseItemDto> GetChannelFolder(string userId, CancellationToken cancellationToken);
-
- /// <summary>
- /// Downloads the channel item.
- /// </summary>
- /// <param name="item">The item.</param>
- /// <param name="destinationPath">The destination path.</param>
- /// <param name="progress">The progress.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
- Task DownloadChannelItem(BaseItem item, string destinationPath, IProgress<double> progress, CancellationToken cancellationToken);
}
}
diff --git a/MediaBrowser.Controller/Chapters/ChapterResponse.cs b/MediaBrowser.Controller/Chapters/ChapterResponse.cs
deleted file mode 100644
index 3c1b8ed07..000000000
--- a/MediaBrowser.Controller/Chapters/ChapterResponse.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-using MediaBrowser.Model.Chapters;
-using System.Collections.Generic;
-
-namespace MediaBrowser.Controller.Chapters
-{
- public class ChapterResponse
- {
- /// <summary>
- /// Gets or sets the chapters.
- /// </summary>
- /// <value>The chapters.</value>
- public List<RemoteChapterInfo> Chapters { get; set; }
-
- public ChapterResponse()
- {
- Chapters = new List<RemoteChapterInfo>();
- }
- }
-} \ No newline at end of file
diff --git a/MediaBrowser.Controller/Chapters/ChapterSearchRequest.cs b/MediaBrowser.Controller/Chapters/ChapterSearchRequest.cs
deleted file mode 100644
index 982dc35bb..000000000
--- a/MediaBrowser.Controller/Chapters/ChapterSearchRequest.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Entities;
-using System;
-using System.Collections.Generic;
-
-namespace MediaBrowser.Controller.Chapters
-{
- public class ChapterSearchRequest : IHasProviderIds
- {
- public string Language { get; set; }
-
- public VideoContentType ContentType { get; set; }
-
- public string MediaPath { get; set; }
- public string SeriesName { get; set; }
- public string Name { get; set; }
- public int? IndexNumber { get; set; }
- public int? IndexNumberEnd { get; set; }
- public int? ParentIndexNumber { get; set; }
- public int? ProductionYear { get; set; }
- public long? RuntimeTicks { get; set; }
- public Dictionary<string, string> ProviderIds { get; set; }
-
- public bool SearchAllProviders { get; set; }
-
- public ChapterSearchRequest()
- {
- ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
- }
- }
-} \ No newline at end of file
diff --git a/MediaBrowser.Controller/Chapters/IChapterManager.cs b/MediaBrowser.Controller/Chapters/IChapterManager.cs
index 27e06fb8d..4b39e66cc 100644
--- a/MediaBrowser.Controller/Chapters/IChapterManager.cs
+++ b/MediaBrowser.Controller/Chapters/IChapterManager.cs
@@ -1,6 +1,4 @@
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Model.Chapters;
-using System.Collections.Generic;
+using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Configuration;
@@ -14,12 +12,6 @@ namespace MediaBrowser.Controller.Chapters
public interface IChapterManager
{
/// <summary>
- /// Adds the parts.
- /// </summary>
- /// <param name="chapterProviders">The chapter providers.</param>
- void AddParts(IEnumerable<IChapterProvider> chapterProviders);
-
- /// <summary>
/// Gets the chapters.
/// </summary>
/// <param name="itemId">The item identifier.</param>
@@ -36,43 +28,6 @@ namespace MediaBrowser.Controller.Chapters
Task SaveChapters(string itemId, List<ChapterInfo> chapters, CancellationToken cancellationToken);
/// <summary>
- /// Searches the specified video.
- /// </summary>
- /// <param name="video">The video.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task{IEnumerable{RemoteChapterResult}}.</returns>
- Task<IEnumerable<RemoteChapterResult>> Search(Video video, CancellationToken cancellationToken);
-
- /// <summary>
- /// Searches the specified request.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task{IEnumerable{RemoteChapterResult}}.</returns>
- Task<IEnumerable<RemoteChapterResult>> Search(ChapterSearchRequest request, CancellationToken cancellationToken);
-
- /// <summary>
- /// Gets the chapters.
- /// </summary>
- /// <param name="id">The identifier.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task{ChapterResponse}.</returns>
- Task<ChapterResponse> GetChapters(string id, CancellationToken cancellationToken);
-
- /// <summary>
- /// Gets the providers.
- /// </summary>
- /// <param name="itemId">The item identifier.</param>
- /// <returns>IEnumerable{ChapterProviderInfo}.</returns>
- IEnumerable<ChapterProviderInfo> GetProviders(string itemId);
-
- /// <summary>
- /// Gets the providers.
- /// </summary>
- /// <returns>IEnumerable{ChapterProviderInfo}.</returns>
- IEnumerable<ChapterProviderInfo> GetProviders();
-
- /// <summary>
/// Gets the configuration.
/// </summary>
/// <returns>ChapterOptions.</returns>
diff --git a/MediaBrowser.Controller/Chapters/IChapterProvider.cs b/MediaBrowser.Controller/Chapters/IChapterProvider.cs
deleted file mode 100644
index a7505347b..000000000
--- a/MediaBrowser.Controller/Chapters/IChapterProvider.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Chapters;
-using System.Collections.Generic;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.Controller.Chapters
-{
- public interface IChapterProvider
- {
- /// <summary>
- /// Gets the name.
- /// </summary>
- /// <value>The name.</value>
- string Name { get; }
-
- /// <summary>
- /// Gets the supported media types.
- /// </summary>
- /// <value>The supported media types.</value>
- IEnumerable<VideoContentType> SupportedMediaTypes { get; }
-
- /// <summary>
- /// Searches the specified request.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task{IEnumerable{RemoteChapterResult}}.</returns>
- Task<IEnumerable<RemoteChapterResult>> Search(ChapterSearchRequest request, CancellationToken cancellationToken);
-
- /// <summary>
- /// Gets the chapters.
- /// </summary>
- /// <param name="id">The identifier.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task{ChapterResponse}.</returns>
- Task<ChapterResponse> GetChapters(string id, CancellationToken cancellationToken);
- }
-}
diff --git a/MediaBrowser.Controller/Dlna/IDeviceDiscovery.cs b/MediaBrowser.Controller/Dlna/IDeviceDiscovery.cs
index e8083b363..d2c5b9e4e 100644
--- a/MediaBrowser.Controller/Dlna/IDeviceDiscovery.cs
+++ b/MediaBrowser.Controller/Dlna/IDeviceDiscovery.cs
@@ -1,10 +1,20 @@
using System;
+using System.Collections.Generic;
+using System.Net;
+using MediaBrowser.Model.Events;
namespace MediaBrowser.Controller.Dlna
{
public interface IDeviceDiscovery
{
- event EventHandler<SsdpMessageEventArgs> DeviceDiscovered;
- event EventHandler<SsdpMessageEventArgs> DeviceLeft;
+ event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceDiscovered;
+ event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceLeft;
+ }
+
+ public class UpnpDeviceInfo
+ {
+ public Uri Location { get; set; }
+ public Dictionary<string, string> Headers { get; set; }
+ public IPEndPoint LocalEndPoint { get; set; }
}
}
diff --git a/MediaBrowser.Controller/Dlna/ISsdpHandler.cs b/MediaBrowser.Controller/Dlna/ISsdpHandler.cs
index e4126ddcf..ec3a00aad 100644
--- a/MediaBrowser.Controller/Dlna/ISsdpHandler.cs
+++ b/MediaBrowser.Controller/Dlna/ISsdpHandler.cs
@@ -4,6 +4,5 @@ namespace MediaBrowser.Controller.Dlna
{
public interface ISsdpHandler
{
- event EventHandler<SsdpMessageEventArgs> MessageReceived;
}
}
diff --git a/MediaBrowser.Controller/Entities/AggregateFolder.cs b/MediaBrowser.Controller/Entities/AggregateFolder.cs
index efc450248..7b769deb3 100644
--- a/MediaBrowser.Controller/Entities/AggregateFolder.cs
+++ b/MediaBrowser.Controller/Entities/AggregateFolder.cs
@@ -34,11 +34,26 @@ namespace MediaBrowser.Controller.Entities
}
}
+ [IgnoreDataMember]
+ public override bool IsPhysicalRoot
+ {
+ get { return true; }
+ }
+
public override bool CanDelete()
{
return false;
}
+ [IgnoreDataMember]
+ public override bool SupportsPlayedStatus
+ {
+ get
+ {
+ return false;
+ }
+ }
+
/// <summary>
/// The _virtual children
/// </summary>
diff --git a/MediaBrowser.Controller/Entities/Audio/Audio.cs b/MediaBrowser.Controller/Entities/Audio/Audio.cs
index 1af55a389..56c3248f3 100644
--- a/MediaBrowser.Controller/Entities/Audio/Audio.cs
+++ b/MediaBrowser.Controller/Entities/Audio/Audio.cs
@@ -23,8 +23,7 @@ namespace MediaBrowser.Controller.Entities.Audio
IHasMusicGenres,
IHasLookupInfo<SongInfo>,
IHasMediaSources,
- IThemeMedia,
- IArchivable
+ IThemeMedia
{
public List<ChannelMediaInfo> ChannelMediaSources { get; set; }
@@ -61,9 +60,18 @@ namespace MediaBrowser.Controller.Entities.Audio
}
[IgnoreDataMember]
+ public override bool SupportsPlayedStatus
+ {
+ get
+ {
+ return true;
+ }
+ }
+
+ [IgnoreDataMember]
public override bool SupportsAddingToPlaylist
{
- get { return LocationType == LocationType.FileSystem && RunTimeTicks.HasValue; }
+ get { return true; }
}
[IgnoreDataMember]
@@ -84,21 +92,6 @@ namespace MediaBrowser.Controller.Entities.Audio
}
}
- [IgnoreDataMember]
- public bool IsArchive
- {
- get
- {
- if (string.IsNullOrWhiteSpace(Path))
- {
- return false;
- }
- var ext = System.IO.Path.GetExtension(Path) ?? string.Empty;
-
- return new[] { ".zip", ".rar", ".7z" }.Contains(ext, StringComparer.OrdinalIgnoreCase);
- }
- }
-
public override bool CanDownload()
{
var locationType = LocationType;
@@ -262,7 +255,7 @@ namespace MediaBrowser.Controller.Entities.Audio
Protocol = locationType == LocationType.Remote ? MediaProtocol.Http : MediaProtocol.File,
MediaStreams = MediaSourceManager.GetMediaStreams(i.Id).ToList(),
Name = i.Name,
- Path = enablePathSubstituion ? GetMappedPath(i.Path, locationType) : i.Path,
+ Path = enablePathSubstituion ? GetMappedPath(i, i.Path, locationType) : i.Path,
RunTimeTicks = i.RunTimeTicks,
Container = i.Container,
Size = i.Size
diff --git a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
index 1f3b0c92a..3f457d89a 100644
--- a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
+++ b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
@@ -17,6 +17,9 @@ namespace MediaBrowser.Controller.Entities.Audio
/// </summary>
public class MusicAlbum : Folder, IHasAlbumArtist, IHasArtist, IHasMusicGenres, IHasLookupInfo<AlbumInfo>, IMetadataContainer
{
+ public List<string> AlbumArtists { get; set; }
+ public List<string> Artists { get; set; }
+
public MusicAlbum()
{
Artists = new List<string>();
@@ -49,6 +52,15 @@ namespace MediaBrowser.Controller.Entities.Audio
}
[IgnoreDataMember]
+ public override bool SupportsPlayedStatus
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ [IgnoreDataMember]
public override bool SupportsCumulativeRunTimeTicks
{
get
@@ -83,8 +95,6 @@ namespace MediaBrowser.Controller.Entities.Audio
get { return false; }
}
- public List<string> AlbumArtists { get; set; }
-
/// <summary>
/// Gets the tracks.
/// </summary>
@@ -103,8 +113,6 @@ namespace MediaBrowser.Controller.Entities.Audio
return Tracks;
}
- public List<string> Artists { get; set; }
-
public override List<string> GetUserDataKeys()
{
var list = base.GetUserDataKeys();
diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
index 81d1deaa2..a31f04fc4 100644
--- a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
+++ b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
@@ -16,7 +16,7 @@ namespace MediaBrowser.Controller.Entities.Audio
/// <summary>
/// Class MusicArtist
/// </summary>
- public class MusicArtist : Folder, IMetadataContainer, IItemByName, IHasMusicGenres, IHasDualAccess, IHasProductionLocations, IHasLookupInfo<ArtistInfo>
+ public class MusicArtist : Folder, IMetadataContainer, IItemByName, IHasMusicGenres, IHasDualAccess, IHasLookupInfo<ArtistInfo>
{
[IgnoreDataMember]
public bool IsAccessedByName
@@ -24,8 +24,6 @@ namespace MediaBrowser.Controller.Entities.Audio
get { return ParentId == Guid.Empty; }
}
- public List<string> ProductionLocations { get; set; }
-
[IgnoreDataMember]
public override bool IsFolder
{
@@ -50,6 +48,15 @@ namespace MediaBrowser.Controller.Entities.Audio
get { return true; }
}
+ [IgnoreDataMember]
+ public override bool SupportsPlayedStatus
+ {
+ get
+ {
+ return false;
+ }
+ }
+
public override bool CanDelete()
{
return !IsAccessedByName;
@@ -111,11 +118,6 @@ namespace MediaBrowser.Controller.Entities.Audio
return base.ValidateChildrenInternal(progress, cancellationToken, recursive, refreshChildMetadata, refreshOptions, directoryService);
}
- public MusicArtist()
- {
- ProductionLocations = new List<string>();
- }
-
public override List<string> GetUserDataKeys()
{
var list = base.GetUserDataKeys();
diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs
index 55aaf04ff..7eb84fc80 100644
--- a/MediaBrowser.Controller/Entities/BaseItem.cs
+++ b/MediaBrowser.Controller/Entities/BaseItem.cs
@@ -37,6 +37,8 @@ namespace MediaBrowser.Controller.Entities
{
protected BaseItem()
{
+ ThemeSongIds = new List<Guid>();
+ ThemeVideoIds = new List<Guid>();
Keywords = new List<string>();
Tags = new List<string>();
Genres = new List<string>();
@@ -44,6 +46,8 @@ namespace MediaBrowser.Controller.Entities
ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
LockedFields = new List<MetadataFields>();
ImageInfos = new List<ItemImageInfo>();
+ InheritedTags = new List<string>();
+ ProductionLocations = new List<string>();
}
public static readonly char[] SlugReplaceChars = { '?', '/', '&' };
@@ -64,6 +68,9 @@ namespace MediaBrowser.Controller.Entities
public static string ThemeSongFilename = "theme";
public static string ThemeVideosFolderName = "backdrops";
+ public List<Guid> ThemeSongIds { get; set; }
+ public List<Guid> ThemeVideoIds { get; set; }
+
[IgnoreDataMember]
public string PreferredMetadataCountryCode { get; set; }
[IgnoreDataMember]
@@ -72,6 +79,8 @@ namespace MediaBrowser.Controller.Entities
public long? Size { get; set; }
public string Container { get; set; }
public string ShortOverview { get; set; }
+ [IgnoreDataMember]
+ public string Tagline { get; set; }
public List<ItemImageInfo> ImageInfos { get; set; }
@@ -115,6 +124,31 @@ namespace MediaBrowser.Controller.Entities
public bool IsInMixedFolder { get; set; }
[IgnoreDataMember]
+ protected virtual bool SupportsIsInMixedFolderDetection
+ {
+ get { return false; }
+ }
+
+ [IgnoreDataMember]
+ public virtual bool SupportsPlayedStatus
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ public bool DetectIsInMixedFolder()
+ {
+ if (SupportsIsInMixedFolderDetection)
+ {
+
+ }
+
+ return IsInMixedFolder;
+ }
+
+ [IgnoreDataMember]
public virtual bool SupportsRemoteImageDownloading
{
get
@@ -254,6 +288,19 @@ namespace MediaBrowser.Controller.Entities
}
}
+ [IgnoreDataMember]
+ public string ExternalSeriesId { get; set; }
+
+ [IgnoreDataMember]
+ public string ExternalSeriesIdLegacy
+ {
+ get { return this.GetProviderId("ProviderExternalSeriesId"); }
+ set
+ {
+ this.SetProviderId("ProviderExternalSeriesId", value);
+ }
+ }
+
/// <summary>
/// Gets or sets the etag.
/// </summary>
@@ -408,7 +455,7 @@ namespace MediaBrowser.Controller.Entities
public virtual bool IsInternetMetadataEnabled()
{
- return ConfigurationManager.Configuration.EnableInternetProviders;
+ return LibraryManager.GetLibraryOptions(this).EnableInternetProviders;
}
public virtual bool CanDelete()
@@ -784,6 +831,9 @@ namespace MediaBrowser.Controller.Entities
[IgnoreDataMember]
public int InheritedParentalRatingValue { get; set; }
+ [IgnoreDataMember]
+ public List<string> InheritedTags { get; set; }
+
/// <summary>
/// Gets or sets the critic rating.
/// </summary>
@@ -841,6 +891,7 @@ namespace MediaBrowser.Controller.Entities
public List<string> Tags { get; set; }
public List<string> Keywords { get; set; }
+ public List<string> ProductionLocations { get; set; }
/// <summary>
/// Gets or sets the home page URL.
@@ -956,7 +1007,7 @@ namespace MediaBrowser.Controller.Entities
/// Loads the theme songs.
/// </summary>
/// <returns>List{Audio.Audio}.</returns>
- private IEnumerable<Audio.Audio> LoadThemeSongs(List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
+ private static IEnumerable<Audio.Audio> LoadThemeSongs(List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
{
var files = fileSystemChildren.Where(i => i.IsDirectory)
.Where(i => string.Equals(i.Name, ThemeSongsFolderName, StringComparison.OrdinalIgnoreCase))
@@ -992,7 +1043,7 @@ namespace MediaBrowser.Controller.Entities
/// Loads the video backdrops.
/// </summary>
/// <returns>List{Video}.</returns>
- private IEnumerable<Video> LoadThemeVideos(IEnumerable<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
+ private static IEnumerable<Video> LoadThemeVideos(IEnumerable<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
{
var files = fileSystemChildren.Where(i => i.IsDirectory)
.Where(i => string.Equals(i.Name, ThemeVideosFolderName, StringComparison.OrdinalIgnoreCase))
@@ -1078,6 +1129,12 @@ namespace MediaBrowser.Controller.Entities
get { return true; }
}
+ [IgnoreDataMember]
+ public virtual bool SupportsThemeMedia
+ {
+ get { return false; }
+ }
+
/// <summary>
/// Refreshes owned items such as trailers, theme videos, special features, etc.
/// Returns true or false indicating if changes were found.
@@ -1096,14 +1153,13 @@ namespace MediaBrowser.Controller.Entities
if (LocationType == LocationType.FileSystem && GetParent() != null)
{
- var hasThemeMedia = this as IHasThemeMedia;
- if (hasThemeMedia != null)
+ if (SupportsThemeMedia)
{
- if (!IsInMixedFolder)
+ if (!DetectIsInMixedFolder())
{
- themeSongsChanged = await RefreshThemeSongs(hasThemeMedia, options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
+ themeSongsChanged = await RefreshThemeSongs(this, options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
- themeVideosChanged = await RefreshThemeVideos(hasThemeMedia, options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
+ themeVideosChanged = await RefreshThemeVideos(this, options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
}
}
@@ -1141,7 +1197,7 @@ namespace MediaBrowser.Controller.Entities
return itemsChanged;
}
- private async Task<bool> RefreshThemeVideos(IHasThemeMedia item, MetadataRefreshOptions options, IEnumerable<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken)
+ private static async Task<bool> RefreshThemeVideos(BaseItem item, MetadataRefreshOptions options, IEnumerable<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken)
{
var newThemeVideos = LoadThemeVideos(fileSystemChildren, options.DirectoryService).ToList();
@@ -1172,7 +1228,7 @@ namespace MediaBrowser.Controller.Entities
/// <summary>
/// Refreshes the theme songs.
/// </summary>
- private async Task<bool> RefreshThemeSongs(IHasThemeMedia item, MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken)
+ private static async Task<bool> RefreshThemeSongs(BaseItem item, MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken)
{
var newThemeSongs = LoadThemeSongs(fileSystemChildren, options.DirectoryService).ToList();
var newThemeSongIds = newThemeSongs.Select(i => i.Id).ToList();
@@ -1249,7 +1305,15 @@ namespace MediaBrowser.Controller.Entities
{
var current = this;
- return current.IsInMixedFolder == newItem.IsInMixedFolder;
+ if (!SupportsIsInMixedFolderDetection)
+ {
+ if (current.IsInMixedFolder != newItem.IsInMixedFolder)
+ {
+ return false;
+ }
+ }
+
+ return true;
}
public void AfterMetadataRefresh()
@@ -1324,7 +1388,9 @@ namespace MediaBrowser.Controller.Entities
return false;
}
- return ConfigurationManager.Configuration.SaveLocalMeta;
+ var libraryOptions = LibraryManager.GetLibraryOptions(this);
+
+ return libraryOptions.SaveLocalMetadata;
}
/// <summary>
@@ -2107,14 +2173,11 @@ namespace MediaBrowser.Controller.Entities
return hasChanges;
}
- protected static string GetMappedPath(string path, LocationType locationType)
+ protected static string GetMappedPath(BaseItem item, string path, LocationType locationType)
{
if (locationType == LocationType.FileSystem || locationType == LocationType.Offline)
{
- foreach (var map in ConfigurationManager.Configuration.PathSubstitutions)
- {
- path = LibraryManager.SubstitutePath(path, map.From, map.To);
- }
+ return LibraryManager.GetPathAfterNetworkSubstitution(path, item);
}
return path;
diff --git a/MediaBrowser.Controller/Entities/CollectionFolder.cs b/MediaBrowser.Controller/Entities/CollectionFolder.cs
index 30ea26eb6..41e277b7c 100644
--- a/MediaBrowser.Controller/Entities/CollectionFolder.cs
+++ b/MediaBrowser.Controller/Entities/CollectionFolder.cs
@@ -38,6 +38,15 @@ namespace MediaBrowser.Controller.Entities
}
}
+ [IgnoreDataMember]
+ public override bool SupportsPlayedStatus
+ {
+ get
+ {
+ return false;
+ }
+ }
+
public override bool CanDelete()
{
return false;
@@ -48,24 +57,14 @@ namespace MediaBrowser.Controller.Entities
private static readonly Dictionary<string, LibraryOptions> LibraryOptions = new Dictionary<string, LibraryOptions>();
public LibraryOptions GetLibraryOptions()
{
- lock (LibraryOptions)
- {
- LibraryOptions options;
- if (!LibraryOptions.TryGetValue(Path, out options))
- {
- options = LoadLibraryOptions();
- LibraryOptions[Path] = options;
- }
-
- return options;
- }
+ return GetLibraryOptions(Path);
}
- private LibraryOptions LoadLibraryOptions()
+ private static LibraryOptions LoadLibraryOptions(string path)
{
try
{
- var result = XmlSerializer.DeserializeFromFile(typeof(LibraryOptions), GetLibraryOptionsPath(Path)) as LibraryOptions;
+ var result = XmlSerializer.DeserializeFromFile(typeof(LibraryOptions), GetLibraryOptionsPath(path)) as LibraryOptions;
if (result == null)
{
@@ -100,13 +99,28 @@ namespace MediaBrowser.Controller.Entities
SaveLibraryOptions(Path, options);
}
+ public static LibraryOptions GetLibraryOptions(string path)
+ {
+ lock (LibraryOptions)
+ {
+ LibraryOptions options;
+ if (!LibraryOptions.TryGetValue(path, out options))
+ {
+ options = LoadLibraryOptions(path);
+ LibraryOptions[path] = options;
+ }
+
+ return options;
+ }
+ }
+
public static void SaveLibraryOptions(string path, LibraryOptions options)
{
lock (LibraryOptions)
{
LibraryOptions[path] = options;
- options.SchemaVersion = 2;
+ options.SchemaVersion = 3;
XmlSerializer.SerializeToFile(options, GetLibraryOptionsPath(path));
}
}
diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs
index f1d8def4b..1e466ec08 100644
--- a/MediaBrowser.Controller/Entities/Folder.cs
+++ b/MediaBrowser.Controller/Entities/Folder.cs
@@ -22,13 +22,18 @@ namespace MediaBrowser.Controller.Entities
/// <summary>
/// Class Folder
/// </summary>
- public class Folder : BaseItem, IHasThemeMedia
+ public class Folder : BaseItem
{
public static IUserManager UserManager { get; set; }
public static IUserViewManager UserViewManager { get; set; }
- public List<Guid> ThemeSongIds { get; set; }
- public List<Guid> ThemeVideoIds { get; set; }
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is root.
+ /// </summary>
+ /// <value><c>true</c> if this instance is root; otherwise, <c>false</c>.</value>
+ public bool IsRoot { get; set; }
+
+ public virtual List<LinkedChild> LinkedChildren { get; set; }
[IgnoreDataMember]
public DateTime? DateLastMediaAdded { get; set; }
@@ -36,9 +41,12 @@ namespace MediaBrowser.Controller.Entities
public Folder()
{
LinkedChildren = new List<LinkedChild>();
+ }
- ThemeSongIds = new List<Guid>();
- ThemeVideoIds = new List<Guid>();
+ [IgnoreDataMember]
+ public override bool SupportsThemeMedia
+ {
+ get { return true; }
}
[IgnoreDataMember]
@@ -47,6 +55,21 @@ namespace MediaBrowser.Controller.Entities
get { return false; }
}
+ [IgnoreDataMember]
+ public virtual bool IsPhysicalRoot
+ {
+ get { return false; }
+ }
+
+ [IgnoreDataMember]
+ public override bool SupportsPlayedStatus
+ {
+ get
+ {
+ return true;
+ }
+ }
+
/// <summary>
/// Gets a value indicating whether this instance is folder.
/// </summary>
@@ -117,19 +140,6 @@ namespace MediaBrowser.Controller.Entities
return true;
}
- /// <summary>
- /// Gets or sets a value indicating whether this instance is physical root.
- /// </summary>
- /// <value><c>true</c> if this instance is physical root; otherwise, <c>false</c>.</value>
- public bool IsPhysicalRoot { get; set; }
- /// <summary>
- /// Gets or sets a value indicating whether this instance is root.
- /// </summary>
- /// <value><c>true</c> if this instance is root; otherwise, <c>false</c>.</value>
- public bool IsRoot { get; set; }
-
- public virtual List<LinkedChild> LinkedChildren { get; set; }
-
[IgnoreDataMember]
protected virtual bool SupportsShortcutChildren
{
@@ -178,8 +188,6 @@ namespace MediaBrowser.Controller.Entities
item.SetParent(null);
}
- #region Indexing
-
/// <summary>
/// Returns the valid set of index by options for this folder type.
/// Override or extend to modify.
@@ -207,8 +215,6 @@ namespace MediaBrowser.Controller.Entities
get { return GetIndexByOptions(); }
}
- #endregion
-
/// <summary>
/// Gets the actual children.
/// </summary>
@@ -801,18 +807,6 @@ namespace MediaBrowser.Controller.Entities
return true;
}
- if (query.HasThemeSong.HasValue)
- {
- Logger.Debug("Query requires post-filtering due to HasThemeSong");
- return true;
- }
-
- if (query.HasThemeVideo.HasValue)
- {
- Logger.Debug("Query requires post-filtering due to HasThemeVideo");
- return true;
- }
-
// Filter by VideoType
if (query.VideoTypes.Length > 0)
{
@@ -1149,29 +1143,19 @@ namespace MediaBrowser.Controller.Entities
return LinkedChildren
.Select(i =>
{
- var requiresPostFilter = true;
-
- if (!string.IsNullOrWhiteSpace(i.Path))
- {
- requiresPostFilter = false;
-
- if (!locations.Any(l => FileSystem.ContainsSubPath(l, i.Path)))
- {
- return null;
- }
- }
-
var child = GetLinkedChild(i);
- if (requiresPostFilter && child != null)
+ if (child != null)
{
- if (string.IsNullOrWhiteSpace(child.Path))
+ var childLocationType = child.LocationType;
+ if (childLocationType == LocationType.Remote || childLocationType == LocationType.Virtual)
{
- Logger.Debug("Found LinkedChild with null path: {0}", child.Name);
- return child;
+ if (!child.IsVisibleStandalone(user))
+ {
+ return null;
+ }
}
-
- if (!locations.Any(l => FileSystem.ContainsSubPath(l, child.Path)))
+ else if (childLocationType == LocationType.FileSystem && !locations.Any(l => FileSystem.ContainsSubPath(l, child.Path)))
{
return null;
}
diff --git a/MediaBrowser.Controller/Entities/Game.cs b/MediaBrowser.Controller/Entities/Game.cs
index 24910498f..aa2ccf4bc 100644
--- a/MediaBrowser.Controller/Entities/Game.cs
+++ b/MediaBrowser.Controller/Entities/Game.cs
@@ -8,19 +8,14 @@ using System.Runtime.Serialization;
namespace MediaBrowser.Controller.Entities
{
- public class Game : BaseItem, IHasTrailers, IHasThemeMedia, IHasScreenshots, ISupportsPlaceHolders, IHasLookupInfo<GameInfo>
+ public class Game : BaseItem, IHasTrailers, IHasScreenshots, ISupportsPlaceHolders, IHasLookupInfo<GameInfo>
{
- public List<Guid> ThemeSongIds { get; set; }
- public List<Guid> ThemeVideoIds { get; set; }
-
public Game()
{
MultiPartGameFiles = new List<string>();
RemoteTrailers = new List<MediaUrl>();
LocalTrailerIds = new List<Guid>();
RemoteTrailerIds = new List<Guid>();
- ThemeSongIds = new List<Guid>();
- ThemeVideoIds = new List<Guid>();
}
public List<Guid> LocalTrailerIds { get; set; }
@@ -39,6 +34,12 @@ namespace MediaBrowser.Controller.Entities
get { return true; }
}
+ [IgnoreDataMember]
+ public override bool SupportsThemeMedia
+ {
+ get { return true; }
+ }
+
/// <summary>
/// Gets or sets the remote trailers.
/// </summary>
@@ -98,7 +99,7 @@ namespace MediaBrowser.Controller.Entities
public override IEnumerable<string> GetDeletePaths()
{
- if (!IsInMixedFolder)
+ if (!DetectIsInMixedFolder())
{
return new[] { System.IO.Path.GetDirectoryName(Path) };
}
diff --git a/MediaBrowser.Controller/Entities/GameSystem.cs b/MediaBrowser.Controller/Entities/GameSystem.cs
index 1c09ee507..9e00ab260 100644
--- a/MediaBrowser.Controller/Entities/GameSystem.cs
+++ b/MediaBrowser.Controller/Entities/GameSystem.cs
@@ -26,6 +26,15 @@ namespace MediaBrowser.Controller.Entities
}
}
+ [IgnoreDataMember]
+ public override bool SupportsPlayedStatus
+ {
+ get
+ {
+ return false;
+ }
+ }
+
/// <summary>
/// Gets or sets the game system.
/// </summary>
diff --git a/MediaBrowser.Controller/Entities/IArchivable.cs b/MediaBrowser.Controller/Entities/IArchivable.cs
deleted file mode 100644
index 575d203a7..000000000
--- a/MediaBrowser.Controller/Entities/IArchivable.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-
-namespace MediaBrowser.Controller.Entities
-{
- public interface IArchivable
- {
- bool IsArchive { get; }
- }
-}
diff --git a/MediaBrowser.Controller/Entities/IHasImages.cs b/MediaBrowser.Controller/Entities/IHasImages.cs
index a38b7394d..1ab0566e0 100644
--- a/MediaBrowser.Controller/Entities/IHasImages.cs
+++ b/MediaBrowser.Controller/Entities/IHasImages.cs
@@ -150,11 +150,7 @@ namespace MediaBrowser.Controller.Entities
/// <value><c>true</c> if [supports local metadata]; otherwise, <c>false</c>.</value>
bool SupportsLocalMetadata { get; }
- /// <summary>
- /// Gets a value indicating whether this instance is in mixed folder.
- /// </summary>
- /// <value><c>true</c> if this instance is in mixed folder; otherwise, <c>false</c>.</value>
- bool IsInMixedFolder { get; }
+ bool DetectIsInMixedFolder();
/// <summary>
/// Gets a value indicating whether this instance is locked.
diff --git a/MediaBrowser.Controller/Entities/IHasMetadata.cs b/MediaBrowser.Controller/Entities/IHasMetadata.cs
index cf2f7db64..aee58b445 100644
--- a/MediaBrowser.Controller/Entities/IHasMetadata.cs
+++ b/MediaBrowser.Controller/Entities/IHasMetadata.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
namespace MediaBrowser.Controller.Entities
{
@@ -62,5 +63,7 @@ namespace MediaBrowser.Controller.Entities
int? GetInheritedParentalRatingValue();
int InheritedParentalRatingValue { get; set; }
+ List<string> GetInheritedTags();
+ List<string> InheritedTags { get; set; }
}
}
diff --git a/MediaBrowser.Controller/Entities/IHasProductionLocations.cs b/MediaBrowser.Controller/Entities/IHasProductionLocations.cs
deleted file mode 100644
index e4652fa8d..000000000
--- a/MediaBrowser.Controller/Entities/IHasProductionLocations.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-
-namespace MediaBrowser.Controller.Entities
-{
- /// <summary>
- /// Interface IHasProductionLocations
- /// </summary>
- public interface IHasProductionLocations
- {
- /// <summary>
- /// Gets or sets the production locations.
- /// </summary>
- /// <value>The production locations.</value>
- List<string> ProductionLocations { get; set; }
- }
-
- public static class ProductionLocationExtensions
- {
- public static void AddProductionLocation(this IHasProductionLocations item, string name)
- {
- if (string.IsNullOrWhiteSpace(name))
- {
- throw new ArgumentNullException("name");
- }
-
- if (!item.ProductionLocations.Contains(name, StringComparer.OrdinalIgnoreCase))
- {
- item.ProductionLocations.Add(name);
- }
- }
- }
-}
diff --git a/MediaBrowser.Controller/Entities/IHasTaglines.cs b/MediaBrowser.Controller/Entities/IHasTaglines.cs
deleted file mode 100644
index 8025d6b44..000000000
--- a/MediaBrowser.Controller/Entities/IHasTaglines.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-
-namespace MediaBrowser.Controller.Entities
-{
- /// <summary>
- /// Interface IHasTaglines
- /// </summary>
- public interface IHasTaglines
- {
- /// <summary>
- /// Gets or sets the taglines.
- /// </summary>
- /// <value>The taglines.</value>
- List<string> Taglines { get; set; }
- }
-
- public static class TaglineExtensions
- {
- /// <summary>
- /// Adds the tagline.
- /// </summary>
- /// <param name="tagline">The tagline.</param>
- /// <exception cref="System.ArgumentNullException">tagline</exception>
- public static void AddTagline(this IHasTaglines item, string tagline)
- {
- if (string.IsNullOrWhiteSpace(tagline))
- {
- throw new ArgumentNullException("tagline");
- }
-
- if (!item.Taglines.Contains(tagline, StringComparer.OrdinalIgnoreCase))
- {
- item.Taglines.Add(tagline);
- }
- }
- }
-}
diff --git a/MediaBrowser.Controller/Entities/IHasThemeMedia.cs b/MediaBrowser.Controller/Entities/IHasThemeMedia.cs
deleted file mode 100644
index acc0050ce..000000000
--- a/MediaBrowser.Controller/Entities/IHasThemeMedia.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-using System;
-using System.Collections.Generic;
-
-namespace MediaBrowser.Controller.Entities
-{
- /// <summary>
- /// Interface IHasThemeMedia
- /// </summary>
- public interface IHasThemeMedia
- {
- /// <summary>
- /// Gets or sets the theme song ids.
- /// </summary>
- /// <value>The theme song ids.</value>
- List<Guid> ThemeSongIds { get; set; }
-
- /// <summary>
- /// Gets or sets the theme video ids.
- /// </summary>
- /// <value>The theme video ids.</value>
- List<Guid> ThemeVideoIds { get; set; }
- }
-}
diff --git a/MediaBrowser.Controller/Entities/IHasUserData.cs b/MediaBrowser.Controller/Entities/IHasUserData.cs
index 2495b0ccd..c21e170ae 100644
--- a/MediaBrowser.Controller/Entities/IHasUserData.cs
+++ b/MediaBrowser.Controller/Entities/IHasUserData.cs
@@ -20,5 +20,7 @@ namespace MediaBrowser.Controller.Entities
Task FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, BaseItemDto itemDto, User user);
bool EnableRememberingTrackSelections { get; }
+
+ bool SupportsPlayedStatus { get; }
}
}
diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
index deea63112..fec703b94 100644
--- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
+++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
@@ -2,6 +2,9 @@
using System;
using System.Collections.Generic;
using MediaBrowser.Model.Configuration;
+using System.Linq;
+using MediaBrowser.Controller.Dto;
+using MediaBrowser.Model.Querying;
namespace MediaBrowser.Controller.Entities
{
@@ -101,6 +104,8 @@ namespace MediaBrowser.Controller.Entities
public bool? IsMovie { get; set; }
public bool? IsSports { get; set; }
public bool? IsKids { get; set; }
+ public bool? IsNews { get; set; }
+ public bool? IsSeries { get; set; }
public int? MinPlayers { get; set; }
public int? MaxPlayers { get; set; }
@@ -137,6 +142,7 @@ namespace MediaBrowser.Controller.Entities
public DayOfWeek[] AirDays { get; set; }
public SeriesStatus[] SeriesStatuses { get; set; }
public string AlbumArtistStartsWithOrGreater { get; set; }
+ public string ExternalSeriesId { get; set; }
public string[] AlbumNames { get; set; }
public string[] ArtistNames { get; set; }
@@ -149,11 +155,53 @@ namespace MediaBrowser.Controller.Entities
public Dictionary<string, string> ExcludeProviderIds { get; set; }
public bool EnableGroupByMetadataKey { get; set; }
+ public List<Tuple<string, SortOrder>> OrderBy { get; set; }
+
+ public DateTime? MinDateCreated { get; set; }
+ public DateTime? MinDateLastSaved { get; set; }
+
+ public DtoOptions DtoOptions { get; set; }
+
+ public bool HasField(ItemFields name)
+ {
+ var fields = DtoOptions.Fields;
+
+ switch (name)
+ {
+ case ItemFields.ThemeSongIds:
+ case ItemFields.ThemeVideoIds:
+ case ItemFields.ProductionLocations:
+ case ItemFields.Keywords:
+ case ItemFields.Taglines:
+ case ItemFields.ShortOverview:
+ case ItemFields.CustomRating:
+ case ItemFields.DateCreated:
+ case ItemFields.SortName:
+ case ItemFields.Overview:
+ case ItemFields.OfficialRatingDescription:
+ case ItemFields.HomePageUrl:
+ case ItemFields.VoteCount:
+ case ItemFields.DisplayMediaType:
+ case ItemFields.ServiceName:
+ case ItemFields.Genres:
+ case ItemFields.Studios:
+ case ItemFields.Settings:
+ case ItemFields.OriginalTitle:
+ case ItemFields.Tags:
+ case ItemFields.DateLastMediaAdded:
+ case ItemFields.CriticRatingSummary:
+ return fields.Count == 0 || fields.Contains(name);
+ default:
+ return true;
+ }
+ }
+
public InternalItemsQuery()
{
GroupByPresentationUniqueKey = true;
EnableTotalRecordCount = true;
+ DtoOptions = new DtoOptions();
AlbumNames = new string[] { };
ArtistNames = new string[] { };
ExcludeArtistIds = new string[] { };
@@ -191,6 +239,7 @@ namespace MediaBrowser.Controller.Entities
TrailerTypes = new TrailerType[] { };
AirDays = new DayOfWeek[] { };
SeriesStatuses = new SeriesStatus[] { };
+ OrderBy = new List<Tuple<string, SortOrder>>();
}
public InternalItemsQuery(User user)
diff --git a/MediaBrowser.Controller/Entities/Movies/Movie.cs b/MediaBrowser.Controller/Entities/Movies/Movie.cs
index f0270497c..22cc0316a 100644
--- a/MediaBrowser.Controller/Entities/Movies/Movie.cs
+++ b/MediaBrowser.Controller/Entities/Movies/Movie.cs
@@ -15,24 +15,17 @@ namespace MediaBrowser.Controller.Entities.Movies
/// <summary>
/// Class Movie
/// </summary>
- public class Movie : Video, IHasCriticRating, IHasSpecialFeatures, IHasProductionLocations, IHasBudget, IHasTrailers, IHasThemeMedia, IHasTaglines, IHasAwards, IHasMetascore, IHasLookupInfo<MovieInfo>, ISupportsBoxSetGrouping, IHasOriginalTitle
+ public class Movie : Video, IHasCriticRating, IHasSpecialFeatures, IHasBudget, IHasTrailers, IHasAwards, IHasMetascore, IHasLookupInfo<MovieInfo>, ISupportsBoxSetGrouping, IHasOriginalTitle
{
public List<Guid> SpecialFeatureIds { get; set; }
- public List<Guid> ThemeSongIds { get; set; }
- public List<Guid> ThemeVideoIds { get; set; }
- public List<string> ProductionLocations { get; set; }
-
public Movie()
{
SpecialFeatureIds = new List<Guid>();
RemoteTrailers = new List<MediaUrl>();
LocalTrailerIds = new List<Guid>();
RemoteTrailerIds = new List<Guid>();
- ThemeSongIds = new List<Guid>();
- ThemeVideoIds = new List<Guid>();
Taglines = new List<string>();
- ProductionLocations = new List<string>();
}
public string AwardSummary { get; set; }
@@ -75,13 +68,22 @@ namespace MediaBrowser.Controller.Entities.Movies
set { TmdbCollectionName = value; }
}
+ [IgnoreDataMember]
+ protected override bool SupportsIsInMixedFolderDetection
+ {
+ get
+ {
+ return false;
+ }
+ }
+
protected override async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken)
{
var hasChanges = await base.RefreshedOwnedItems(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
// Must have a parent to have special features
// In other words, it must be part of the Parent/Child tree
- if (LocationType == LocationType.FileSystem && GetParent() != null && !IsInMixedFolder)
+ if (LocationType == LocationType.FileSystem && GetParent() != null && !DetectIsInMixedFolder())
{
var specialFeaturesChanged = await RefreshSpecialFeatures(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
@@ -119,7 +121,7 @@ namespace MediaBrowser.Controller.Entities.Movies
{
var info = GetItemLookupInfo<MovieInfo>();
- if (!IsInMixedFolder)
+ if (!DetectIsInMixedFolder())
{
info.Name = System.IO.Path.GetFileName(ContainingFolderPath);
}
@@ -145,7 +147,7 @@ namespace MediaBrowser.Controller.Entities.Movies
else
{
// Try to get the year from the folder name
- if (!IsInMixedFolder)
+ if (!DetectIsInMixedFolder())
{
info = LibraryManager.ParseName(System.IO.Path.GetFileName(ContainingFolderPath));
diff --git a/MediaBrowser.Controller/Entities/MusicVideo.cs b/MediaBrowser.Controller/Entities/MusicVideo.cs
index 8b749b7a5..6e632a26c 100644
--- a/MediaBrowser.Controller/Entities/MusicVideo.cs
+++ b/MediaBrowser.Controller/Entities/MusicVideo.cs
@@ -6,7 +6,7 @@ using System.Runtime.Serialization;
namespace MediaBrowser.Controller.Entities
{
- public class MusicVideo : Video, IHasArtist, IHasMusicGenres, IHasProductionLocations, IHasBudget, IHasLookupInfo<MusicVideoInfo>
+ public class MusicVideo : Video, IHasArtist, IHasMusicGenres, IHasBudget, IHasLookupInfo<MusicVideoInfo>
{
/// <summary>
/// Gets or sets the budget.
@@ -19,12 +19,10 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
/// <value>The revenue.</value>
public double? Revenue { get; set; }
- public List<string> ProductionLocations { get; set; }
public List<string> Artists { get; set; }
public MusicVideo()
{
- ProductionLocations = new List<string>();
Artists = new List<string>();
}
@@ -37,6 +35,15 @@ namespace MediaBrowser.Controller.Entities
}
}
+ [IgnoreDataMember]
+ protected override bool SupportsIsInMixedFolderDetection
+ {
+ get
+ {
+ return true;
+ }
+ }
+
public override UnratedItem GetBlockUnratedType()
{
return UnratedItem.Music;
@@ -65,7 +72,7 @@ namespace MediaBrowser.Controller.Entities
else
{
// Try to get the year from the folder name
- if (!IsInMixedFolder)
+ if (!DetectIsInMixedFolder())
{
info = LibraryManager.ParseName(System.IO.Path.GetFileName(ContainingFolderPath));
diff --git a/MediaBrowser.Controller/Entities/Photo.cs b/MediaBrowser.Controller/Entities/Photo.cs
index 965616eb5..a42fce8bb 100644
--- a/MediaBrowser.Controller/Entities/Photo.cs
+++ b/MediaBrowser.Controller/Entities/Photo.cs
@@ -1,19 +1,11 @@
using MediaBrowser.Model.Drawing;
-using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
namespace MediaBrowser.Controller.Entities
{
- public class Photo : BaseItem, IHasTaglines
+ public class Photo : BaseItem
{
- public List<string> Taglines { get; set; }
-
- public Photo()
- {
- Taglines = new List<string>();
- }
-
[IgnoreDataMember]
public override bool SupportsLocalMetadata
{
@@ -37,13 +29,13 @@ namespace MediaBrowser.Controller.Entities
{
get
{
- return Album;
+ return AlbumEntity;
}
}
[IgnoreDataMember]
- public PhotoAlbum Album
+ public PhotoAlbum AlbumEntity
{
get
{
diff --git a/MediaBrowser.Controller/Entities/PhotoAlbum.cs b/MediaBrowser.Controller/Entities/PhotoAlbum.cs
index b0ddcfb8c..cb5c5c453 100644
--- a/MediaBrowser.Controller/Entities/PhotoAlbum.cs
+++ b/MediaBrowser.Controller/Entities/PhotoAlbum.cs
@@ -16,6 +16,15 @@ namespace MediaBrowser.Controller.Entities
}
}
+ [IgnoreDataMember]
+ public override bool SupportsPlayedStatus
+ {
+ get
+ {
+ return false;
+ }
+ }
+
protected override bool GetBlockUnratedValue(UserPolicy config)
{
return config.BlockUnratedItems.Contains(UnratedItem.Other);
diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs
index ff3e53b69..62af14159 100644
--- a/MediaBrowser.Controller/Entities/TV/Season.cs
+++ b/MediaBrowser.Controller/Entities/TV/Season.cs
@@ -154,8 +154,6 @@ namespace MediaBrowser.Controller.Entities.TV
Func<BaseItem, bool> filter = i => UserViewBuilder.Filter(i, user, query, UserDataManager, LibraryManager);
- var id = Guid.NewGuid().ToString("N");
-
var items = GetEpisodes(user).Where(filter);
var result = PostFilterAndSort(items, query, false, false);
diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs
index 7e8ba0516..39703f67a 100644
--- a/MediaBrowser.Controller/Entities/TV/Series.cs
+++ b/MediaBrowser.Controller/Entities/TV/Series.cs
@@ -17,17 +17,14 @@ namespace MediaBrowser.Controller.Entities.TV
/// <summary>
/// Class Series
/// </summary>
- public class Series : Folder, IHasTrailers, IHasDisplayOrder, IHasLookupInfo<SeriesInfo>, IHasSpecialFeatures, IMetadataContainer, IHasOriginalTitle
+ public class Series : Folder, IHasTrailers, IHasDisplayOrder, IHasLookupInfo<SeriesInfo>, IMetadataContainer, IHasOriginalTitle
{
- public List<Guid> SpecialFeatureIds { get; set; }
-
public int? AnimeSeriesIndex { get; set; }
public Series()
{
AirDays = new List<DayOfWeek>();
- SpecialFeatureIds = new List<Guid>();
RemoteTrailers = new List<MediaUrl>();
LocalTrailerIds = new List<Guid>();
RemoteTrailerIds = new List<Guid>();
diff --git a/MediaBrowser.Controller/Entities/Trailer.cs b/MediaBrowser.Controller/Entities/Trailer.cs
index 7a987a68e..0bcd5c14e 100644
--- a/MediaBrowser.Controller/Entities/Trailer.cs
+++ b/MediaBrowser.Controller/Entities/Trailer.cs
@@ -10,16 +10,12 @@ namespace MediaBrowser.Controller.Entities
/// <summary>
/// Class Trailer
/// </summary>
- public class Trailer : Video, IHasCriticRating, IHasProductionLocations, IHasBudget, IHasTaglines, IHasMetascore, IHasOriginalTitle, IHasLookupInfo<TrailerInfo>
+ public class Trailer : Video, IHasCriticRating, IHasBudget, IHasMetascore, IHasOriginalTitle, IHasLookupInfo<TrailerInfo>
{
- public List<string> ProductionLocations { get; set; }
-
public Trailer()
{
RemoteTrailers = new List<MediaUrl>();
- Taglines = new List<string>();
Keywords = new List<string>();
- ProductionLocations = new List<string>();
TrailerTypes = new List<TrailerType> { TrailerType.LocalTrailer };
}
@@ -36,12 +32,6 @@ namespace MediaBrowser.Controller.Entities
}
/// <summary>
- /// Gets or sets the taglines.
- /// </summary>
- /// <value>The taglines.</value>
- public List<string> Taglines { get; set; }
-
- /// <summary>
/// Gets or sets the budget.
/// </summary>
/// <value>The budget.</value>
@@ -64,7 +54,7 @@ namespace MediaBrowser.Controller.Entities
info.IsLocalTrailer = TrailerTypes.Contains(TrailerType.LocalTrailer);
- if (!IsInMixedFolder && LocationType == LocationType.FileSystem)
+ if (!DetectIsInMixedFolder() && LocationType == LocationType.FileSystem)
{
info.Name = System.IO.Path.GetFileName(ContainingFolderPath);
}
@@ -90,7 +80,7 @@ namespace MediaBrowser.Controller.Entities
else
{
// Try to get the year from the folder name
- if (!IsInMixedFolder)
+ if (!DetectIsInMixedFolder())
{
info = LibraryManager.ParseName(System.IO.Path.GetFileName(ContainingFolderPath));
diff --git a/MediaBrowser.Controller/Entities/UserRootFolder.cs b/MediaBrowser.Controller/Entities/UserRootFolder.cs
index aff1f5e28..b67817846 100644
--- a/MediaBrowser.Controller/Entities/UserRootFolder.cs
+++ b/MediaBrowser.Controller/Entities/UserRootFolder.cs
@@ -33,6 +33,15 @@ namespace MediaBrowser.Controller.Entities
}
}
+ [IgnoreDataMember]
+ public override bool SupportsPlayedStatus
+ {
+ get
+ {
+ return false;
+ }
+ }
+
private void ClearCache()
{
lock (_childIdsLock)
diff --git a/MediaBrowser.Controller/Entities/UserView.cs b/MediaBrowser.Controller/Entities/UserView.cs
index 35375e7e6..a689e0d09 100644
--- a/MediaBrowser.Controller/Entities/UserView.cs
+++ b/MediaBrowser.Controller/Entities/UserView.cs
@@ -13,7 +13,6 @@ namespace MediaBrowser.Controller.Entities
public class UserView : Folder
{
public string ViewType { get; set; }
- public Guid ParentId { get; set; }
public Guid DisplayParentId { get; set; }
public Guid? UserId { get; set; }
@@ -45,6 +44,15 @@ namespace MediaBrowser.Controller.Entities
return list;
}
+ [IgnoreDataMember]
+ public override bool SupportsPlayedStatus
+ {
+ get
+ {
+ return false;
+ }
+ }
+
public override int GetChildCount(User user)
{
return GetChildren(user, true).Count();
diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs
index 9f3acc3fc..3b7e3c5d2 100644
--- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs
+++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs
@@ -96,7 +96,7 @@ namespace MediaBrowser.Controller.Entities
Limit = query.Limit,
IsAiring = true
- }, CancellationToken.None).ConfigureAwait(false);
+ }, new Dto.DtoOptions(), CancellationToken.None).ConfigureAwait(false);
return GetResult(result);
}
@@ -1497,13 +1497,7 @@ namespace MediaBrowser.Controller.Entities
{
var filterValue = query.HasThemeSong.Value;
- var themeCount = 0;
- var iHasThemeMedia = item as IHasThemeMedia;
-
- if (iHasThemeMedia != null)
- {
- themeCount = iHasThemeMedia.ThemeSongIds.Count;
- }
+ var themeCount = item.ThemeSongIds.Count;
var ok = filterValue ? themeCount > 0 : themeCount == 0;
if (!ok)
@@ -1516,13 +1510,7 @@ namespace MediaBrowser.Controller.Entities
{
var filterValue = query.HasThemeVideo.Value;
- var themeCount = 0;
- var iHasThemeMedia = item as IHasThemeMedia;
-
- if (iHasThemeMedia != null)
- {
- themeCount = iHasThemeMedia.ThemeVideoIds.Count;
- }
+ var themeCount = item.ThemeVideoIds.Count;
var ok = filterValue ? themeCount > 0 : themeCount == 0;
if (!ok)
diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs
index 8809f155c..5aaff6fef 100644
--- a/MediaBrowser.Controller/Entities/Video.cs
+++ b/MediaBrowser.Controller/Entities/Video.cs
@@ -25,8 +25,7 @@ namespace MediaBrowser.Controller.Entities
ISupportsPlaceHolders,
IHasMediaSources,
IHasShortOverview,
- IThemeMedia,
- IArchivable
+ IThemeMedia
{
[IgnoreDataMember]
public string PrimaryVersionId { get; set; }
@@ -45,6 +44,24 @@ namespace MediaBrowser.Controller.Entities
}
}
+ [IgnoreDataMember]
+ public override bool SupportsPlayedStatus
+ {
+ get
+ {
+ return true;
+ }
+ }
+
+ [IgnoreDataMember]
+ protected override bool SupportsIsInMixedFolderDetection
+ {
+ get
+ {
+ return true;
+ }
+ }
+
public override string CreatePresentationUniqueKey()
{
if (!string.IsNullOrWhiteSpace(PrimaryVersionId))
@@ -64,6 +81,12 @@ namespace MediaBrowser.Controller.Entities
}
}
+ [IgnoreDataMember]
+ public override bool SupportsThemeMedia
+ {
+ get { return true; }
+ }
+
public int? TotalBitrate { get; set; }
public ExtraType? ExtraType { get; set; }
@@ -165,7 +188,7 @@ namespace MediaBrowser.Controller.Entities
[IgnoreDataMember]
public override bool SupportsAddingToPlaylist
{
- get { return LocationType == LocationType.FileSystem && RunTimeTicks.HasValue; }
+ get { return true; }
}
[IgnoreDataMember]
@@ -197,21 +220,6 @@ namespace MediaBrowser.Controller.Entities
get { return LocalAlternateVersions.Count > 0; }
}
- [IgnoreDataMember]
- public bool IsArchive
- {
- get
- {
- if (string.IsNullOrWhiteSpace(Path))
- {
- return false;
- }
- var ext = System.IO.Path.GetExtension(Path) ?? string.Empty;
-
- return new[] { ".zip", ".rar", ".7z" }.Contains(ext, StringComparer.OrdinalIgnoreCase);
- }
- }
-
public IEnumerable<Guid> GetAdditionalPartIds()
{
return AdditionalParts.Select(i => LibraryManager.GetNewItemId(i, typeof(Video)));
@@ -480,7 +488,7 @@ namespace MediaBrowser.Controller.Entities
public override IEnumerable<string> GetDeletePaths()
{
- if (!IsInMixedFolder)
+ if (!DetectIsInMixedFolder())
{
return new[] { ContainingFolderPath };
}
@@ -600,7 +608,7 @@ namespace MediaBrowser.Controller.Entities
Protocol = locationType == LocationType.Remote ? MediaProtocol.Http : MediaProtocol.File,
MediaStreams = mediaStreams,
Name = GetMediaSourceName(i, mediaStreams),
- Path = enablePathSubstitution ? GetMappedPath(i.Path, locationType) : i.Path,
+ Path = enablePathSubstitution ? GetMappedPath(i, i.Path, locationType) : i.Path,
RunTimeTicks = i.RunTimeTicks,
Video3DFormat = i.Video3DFormat,
VideoType = i.VideoType,
diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs
index 04268bcea..975c59659 100644
--- a/MediaBrowser.Controller/Library/ILibraryManager.cs
+++ b/MediaBrowser.Controller/Library/ILibraryManager.cs
@@ -506,6 +506,8 @@ namespace MediaBrowser.Controller.Library
/// <returns>QueryResult&lt;BaseItem&gt;.</returns>
QueryResult<BaseItem> QueryItems(InternalItemsQuery query);
+ string GetPathAfterNetworkSubstitution(string path, BaseItem ownerItem = null);
+
/// <summary>
/// Substitutes the path.
/// </summary>
@@ -554,9 +556,10 @@ namespace MediaBrowser.Controller.Library
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
bool IgnoreFile(FileSystemMetadata file, BaseItem parent);
- void AddVirtualFolder(string name, string collectionType, string[] mediaPaths, LibraryOptions options, bool refreshLibrary);
+ void AddVirtualFolder(string name, string collectionType, LibraryOptions options, bool refreshLibrary);
void RemoveVirtualFolder(string name, bool refreshLibrary);
- void AddMediaPath(string virtualFolderName, string path);
+ void AddMediaPath(string virtualFolderName, MediaPathInfo path);
+ void UpdateMediaPath(string virtualFolderName, MediaPathInfo path);
void RemoveMediaPath(string virtualFolderName, string path);
QueryResult<Tuple<BaseItem, ItemCounts>> GetGenres(InternalItemsQuery query);
diff --git a/MediaBrowser.Controller/Library/IMediaSourceManager.cs b/MediaBrowser.Controller/Library/IMediaSourceManager.cs
index a77d88049..1ab0e4cb0 100644
--- a/MediaBrowser.Controller/Library/IMediaSourceManager.cs
+++ b/MediaBrowser.Controller/Library/IMediaSourceManager.cs
@@ -7,6 +7,7 @@ using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
+using System.IO;
namespace MediaBrowser.Controller.Library
{
@@ -60,11 +61,8 @@ namespace MediaBrowser.Controller.Library
/// <summary>
/// Gets the static media source.
/// </summary>
- /// <param name="item">The item.</param>
- /// <param name="mediaSourceId">The media source identifier.</param>
- /// <param name="enablePathSubstitution">if set to <c>true</c> [enable path substitution].</param>
/// <returns>MediaSourceInfo.</returns>
- Task<MediaSourceInfo> GetMediaSource(IHasMediaSources item, string mediaSourceId, bool enablePathSubstitution);
+ Task<MediaSourceInfo> GetMediaSource(IHasMediaSources item, string mediaSourceId, string liveStreamId, bool enablePathSubstitution, CancellationToken cancellationToken);
/// <summary>
/// Opens the media source.
@@ -82,6 +80,8 @@ namespace MediaBrowser.Controller.Library
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task&lt;MediaSourceInfo&gt;.</returns>
Task<MediaSourceInfo> GetLiveStream(string id, CancellationToken cancellationToken);
+
+ Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> GetLiveStreamWithDirectStreamProvider(string id, CancellationToken cancellationToken);
/// <summary>
/// Pings the media source.
@@ -95,8 +95,12 @@ namespace MediaBrowser.Controller.Library
/// Closes the media source.
/// </summary>
/// <param name="id">The live stream identifier.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
- Task CloseLiveStream(string id, CancellationToken cancellationToken);
+ Task CloseLiveStream(string id);
+ }
+
+ public interface IDirectStreamProvider
+ {
+ Task CopyToAsync(Stream stream, CancellationToken cancellationToken);
}
}
diff --git a/MediaBrowser.Controller/Library/IMediaSourceProvider.cs b/MediaBrowser.Controller/Library/IMediaSourceProvider.cs
index 5b033af4a..b0881ba7c 100644
--- a/MediaBrowser.Controller/Library/IMediaSourceProvider.cs
+++ b/MediaBrowser.Controller/Library/IMediaSourceProvider.cs
@@ -3,6 +3,7 @@ using MediaBrowser.Model.Dto;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
+using System;
namespace MediaBrowser.Controller.Library
{
@@ -22,14 +23,13 @@ namespace MediaBrowser.Controller.Library
/// <param name="openToken">The open token.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task&lt;MediaSourceInfo&gt;.</returns>
- Task<MediaSourceInfo> OpenMediaSource(string openToken, CancellationToken cancellationToken);
+ Task<Tuple<MediaSourceInfo,IDirectStreamProvider>> OpenMediaSource(string openToken, CancellationToken cancellationToken);
/// <summary>
/// Closes the media source.
/// </summary>
/// <param name="liveStreamId">The live stream identifier.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
- Task CloseMediaSource(string liveStreamId, CancellationToken cancellationToken);
+ Task CloseMediaSource(string liveStreamId);
}
}
diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
index a8e42749b..8e3c1931b 100644
--- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
+++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
@@ -9,6 +9,7 @@ using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Events;
+using MediaBrowser.Controller.Library;
namespace MediaBrowser.Controller.LiveTv
{
@@ -37,7 +38,7 @@ namespace MediaBrowser.Controller.LiveTv
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{SeriesTimerInfoDto}.</returns>
Task<SeriesTimerInfoDto> GetNewTimerDefaults(string programId, CancellationToken cancellationToken);
-
+
/// <summary>
/// Deletes the recording.
/// </summary>
@@ -51,7 +52,7 @@ namespace MediaBrowser.Controller.LiveTv
/// <param name="recording">The recording.</param>
/// <returns>Task.</returns>
Task DeleteRecording(BaseItem recording);
-
+
/// <summary>
/// Cancels the timer.
/// </summary>
@@ -83,7 +84,7 @@ namespace MediaBrowser.Controller.LiveTv
/// <param name="user">The user.</param>
/// <returns>Task{RecordingInfoDto}.</returns>
Task<BaseItemDto> GetRecording(string id, DtoOptions options, CancellationToken cancellationToken, User user = null);
-
+
/// <summary>
/// Gets the timer.
/// </summary>
@@ -125,14 +126,14 @@ namespace MediaBrowser.Controller.LiveTv
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{QueryResult{SeriesTimerInfoDto}}.</returns>
Task<QueryResult<SeriesTimerInfoDto>> GetSeriesTimers(SeriesTimerQuery query, CancellationToken cancellationToken);
-
+
/// <summary>
/// Gets the channel.
/// </summary>
/// <param name="id">The identifier.</param>
/// <returns>Channel.</returns>
LiveTvChannel GetInternalChannel(string id);
-
+
/// <summary>
/// Gets the recording.
/// </summary>
@@ -156,8 +157,8 @@ namespace MediaBrowser.Controller.LiveTv
/// <param name="mediaSourceId">The media source identifier.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{StreamResponseInfo}.</returns>
- Task<MediaSourceInfo> GetChannelStream(string id, string mediaSourceId, CancellationToken cancellationToken);
-
+ Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> GetChannelStream(string id, string mediaSourceId, CancellationToken cancellationToken);
+
/// <summary>
/// Gets the program.
/// </summary>
@@ -220,9 +221,8 @@ namespace MediaBrowser.Controller.LiveTv
/// Closes the live stream.
/// </summary>
/// <param name="id">The identifier.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
- Task CloseLiveStream(string id, CancellationToken cancellationToken);
+ Task CloseLiveStream(string id);
/// <summary>
/// Gets the guide information.
@@ -242,10 +242,8 @@ namespace MediaBrowser.Controller.LiveTv
/// <summary>
/// Gets the recommended programs internal.
/// </summary>
- /// <param name="query">The query.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task&lt;QueryResult&lt;LiveTvProgram&gt;&gt;.</returns>
- Task<QueryResult<LiveTvProgram>> GetRecommendedProgramsInternal(RecommendedProgramQuery query, CancellationToken cancellationToken);
+ Task<QueryResult<LiveTvProgram>> GetRecommendedProgramsInternal(RecommendedProgramQuery query, DtoOptions options, CancellationToken cancellationToken);
/// <summary>
/// Gets the live tv information.
@@ -303,18 +301,12 @@ namespace MediaBrowser.Controller.LiveTv
/// <summary>
/// Gets the recording media sources.
/// </summary>
- /// <param name="id">The identifier.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task&lt;IEnumerable&lt;MediaSourceInfo&gt;&gt;.</returns>
- Task<IEnumerable<MediaSourceInfo>> GetRecordingMediaSources(string id, CancellationToken cancellationToken);
+ Task<IEnumerable<MediaSourceInfo>> GetRecordingMediaSources(IHasMediaSources item, CancellationToken cancellationToken);
/// <summary>
/// Gets the channel media sources.
/// </summary>
- /// <param name="id">The identifier.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task&lt;IEnumerable&lt;MediaSourceInfo&gt;&gt;.</returns>
- Task<IEnumerable<MediaSourceInfo>> GetChannelMediaSources(string id, CancellationToken cancellationToken);
+ Task<IEnumerable<MediaSourceInfo>> GetChannelMediaSources(IHasMediaSources item, CancellationToken cancellationToken);
/// <summary>
/// Adds the information to recording dto.
@@ -331,8 +323,8 @@ namespace MediaBrowser.Controller.LiveTv
/// <param name="fields">The fields.</param>
/// <param name="user">The user.</param>
/// <returns>Task.</returns>
- Task AddInfoToProgramDto(List<Tuple<BaseItem,BaseItemDto>> programs, List<ItemFields> fields, User user = null);
-
+ Task AddInfoToProgramDto(List<Tuple<BaseItem, BaseItemDto>> programs, List<ItemFields> fields, User user = null);
+
/// <summary>
/// Saves the tuner host.
/// </summary>
@@ -395,7 +387,7 @@ namespace MediaBrowser.Controller.LiveTv
Task<List<ChannelInfo>> GetChannelsForListingsProvider(string id, CancellationToken cancellationToken);
Task<List<ChannelInfo>> GetChannelsFromListingsProviderData(string id, CancellationToken cancellationToken);
- List<IListingsProvider> ListingProviders { get;}
+ List<IListingsProvider> ListingProviders { get; }
event EventHandler<GenericEventArgs<TimerEventInfo>> SeriesTimerCancelled;
event EventHandler<GenericEventArgs<TimerEventInfo>> TimerCancelled;
diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs b/MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs
index 257024d01..1bbd1a008 100644
--- a/MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs
+++ b/MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs
@@ -33,6 +33,7 @@ namespace MediaBrowser.Controller.LiveTv
bool CanDelete(User user);
string SeriesTimerId { get; set; }
+ string TimerId { get; set; }
RecordingStatus Status { get; set; }
DateTime? EndDate { get; set; }
DateTime DateLastSaved { get; set; }
diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvService.cs b/MediaBrowser.Controller/LiveTv/ILiveTvService.cs
index d7d8336d0..cea2d6e21 100644
--- a/MediaBrowser.Controller/LiveTv/ILiveTvService.cs
+++ b/MediaBrowser.Controller/LiveTv/ILiveTvService.cs
@@ -4,6 +4,7 @@ using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Controller.Library;
namespace MediaBrowser.Controller.LiveTv
{
@@ -245,4 +246,14 @@ namespace MediaBrowser.Controller.LiveTv
/// <returns>Task.</returns>
Task<string> CreateSeriesTimer(SeriesTimerInfo info, CancellationToken cancellationToken);
}
+
+ public interface ISupportsDirectStreamProvider
+ {
+ Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> GetChannelStreamWithDirectStreamProvider(string channelId, string streamId, CancellationToken cancellationToken);
+ }
+
+ public interface ISupportsUpdatingDefaults
+ {
+ Task UpdateTimerDefaults(SeriesTimerInfo info, CancellationToken cancellationToken);
+ }
}
diff --git a/MediaBrowser.Controller/LiveTv/ITunerHost.cs b/MediaBrowser.Controller/LiveTv/ITunerHost.cs
index 1e7aa3de5..89d035649 100644
--- a/MediaBrowser.Controller/LiveTv/ITunerHost.cs
+++ b/MediaBrowser.Controller/LiveTv/ITunerHost.cs
@@ -22,9 +22,8 @@ namespace MediaBrowser.Controller.LiveTv
/// <summary>
/// Gets the channels.
/// </summary>
- /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task&lt;IEnumerable&lt;ChannelInfo&gt;&gt;.</returns>
- Task<IEnumerable<ChannelInfo>> GetChannels(CancellationToken cancellationToken);
+ Task<IEnumerable<ChannelInfo>> GetChannels(bool enableCache, CancellationToken cancellationToken);
/// <summary>
/// Gets the tuner infos.
/// </summary>
@@ -38,7 +37,7 @@ namespace MediaBrowser.Controller.LiveTv
/// <param name="streamId">The stream identifier.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task&lt;MediaSourceInfo&gt;.</returns>
- Task<Tuple<MediaSourceInfo,SemaphoreSlim>> GetChannelStream(string channelId, string streamId, CancellationToken cancellationToken);
+ Task<LiveStream> GetChannelStream(string channelId, string streamId, CancellationToken cancellationToken);
/// <summary>
/// Gets the channel stream media sources.
/// </summary>
@@ -46,8 +45,6 @@ namespace MediaBrowser.Controller.LiveTv
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task&lt;List&lt;MediaSourceInfo&gt;&gt;.</returns>
Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(string channelId, CancellationToken cancellationToken);
-
- string ApplyDuration(string streamPath, TimeSpan duration);
}
public interface IConfigurableTunerHost
{
diff --git a/MediaBrowser.Controller/LiveTv/LiveStream.cs b/MediaBrowser.Controller/LiveTv/LiveStream.cs
new file mode 100644
index 000000000..0908c3ecc
--- /dev/null
+++ b/MediaBrowser.Controller/LiveTv/LiveStream.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Model.Dto;
+
+namespace MediaBrowser.Controller.LiveTv
+{
+ public class LiveStream
+ {
+ public MediaSourceInfo OriginalMediaSource { get; set; }
+ public MediaSourceInfo OpenedMediaSource { get; set; }
+ public int ConsumerCount {
+ get { return SharedStreamIds.Count; }
+ }
+ public ITunerHost TunerHost { get; set; }
+ public string OriginalStreamId { get; set; }
+ public bool EnableStreamSharing { get; set; }
+ public string UniqueId = Guid.NewGuid().ToString("N");
+
+ public List<string> SharedStreamIds = new List<string>();
+
+ public LiveStream(MediaSourceInfo mediaSource)
+ {
+ OriginalMediaSource = mediaSource;
+ OpenedMediaSource = mediaSource;
+ EnableStreamSharing = true;
+ }
+
+ public Task Open(CancellationToken cancellationToken)
+ {
+ return OpenInternal(cancellationToken);
+ }
+
+ protected virtual Task OpenInternal(CancellationToken cancellationToken)
+ {
+ return Task.FromResult(true);
+ }
+
+ public virtual Task Close()
+ {
+ return Task.FromResult(true);
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs b/MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs
index e6f472414..e6fefbf72 100644
--- a/MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs
+++ b/MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs
@@ -20,6 +20,7 @@ namespace MediaBrowser.Controller.LiveTv
[IgnoreDataMember]
public bool IsSeries { get; set; }
public string SeriesTimerId { get; set; }
+ public string TimerId { get; set; }
[IgnoreDataMember]
public DateTime StartDate { get; set; }
public RecordingStatus Status { get; set; }
@@ -112,7 +113,7 @@ namespace MediaBrowser.Controller.LiveTv
public override bool CanDelete()
{
- return true;
+ return Status == RecordingStatus.Completed;
}
public override bool IsAuthorizedToDelete(User user)
diff --git a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs
index 50aeed27d..610d4b6f8 100644
--- a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs
+++ b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs
@@ -5,11 +5,12 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.MediaInfo;
using System.Collections.Generic;
+using System.Globalization;
using System.Runtime.Serialization;
namespace MediaBrowser.Controller.LiveTv
{
- public class LiveTvChannel : BaseItem, IHasMediaSources
+ public class LiveTvChannel : BaseItem, IHasMediaSources, IHasProgramAttributes
{
public override List<string> GetUserDataKeys()
{
@@ -81,10 +82,10 @@ namespace MediaBrowser.Controller.LiveTv
if (!string.IsNullOrEmpty(Number))
{
- double.TryParse(Number, out number);
+ double.TryParse(Number, NumberStyles.Any, CultureInfo.InvariantCulture, out number);
}
- return number.ToString("000-") + (Name ?? string.Empty);
+ return number.ToString("00000-") + (Name ?? string.Empty);
}
[IgnoreDataMember]
@@ -137,5 +138,56 @@ namespace MediaBrowser.Controller.LiveTv
{
return false;
}
+
+ [IgnoreDataMember]
+ public bool IsMovie { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is sports.
+ /// </summary>
+ /// <value><c>true</c> if this instance is sports; otherwise, <c>false</c>.</value>
+ [IgnoreDataMember]
+ public bool IsSports { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is series.
+ /// </summary>
+ /// <value><c>true</c> if this instance is series; otherwise, <c>false</c>.</value>
+ [IgnoreDataMember]
+ public bool IsSeries { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is live.
+ /// </summary>
+ /// <value><c>true</c> if this instance is live; otherwise, <c>false</c>.</value>
+ [IgnoreDataMember]
+ public bool IsLive { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is news.
+ /// </summary>
+ /// <value><c>true</c> if this instance is news; otherwise, <c>false</c>.</value>
+ [IgnoreDataMember]
+ public bool IsNews { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is kids.
+ /// </summary>
+ /// <value><c>true</c> if this instance is kids; otherwise, <c>false</c>.</value>
+ [IgnoreDataMember]
+ public bool IsKids { get; set; }
+
+ [IgnoreDataMember]
+ public bool IsPremiere { get; set; }
+
+ [IgnoreDataMember]
+ public bool IsRepeat { get; set; }
+
+ /// <summary>
+ /// Gets or sets the episode title.
+ /// </summary>
+ /// <value>The episode title.</value>
+ [IgnoreDataMember]
+ public string EpisodeTitle { get; set; }
}
}
diff --git a/MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs b/MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs
index f4dba070d..c255630f5 100644
--- a/MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs
+++ b/MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs
@@ -19,6 +19,7 @@ namespace MediaBrowser.Controller.LiveTv
[IgnoreDataMember]
public bool IsSeries { get; set; }
public string SeriesTimerId { get; set; }
+ public string TimerId { get; set; }
[IgnoreDataMember]
public DateTime StartDate { get; set; }
public RecordingStatus Status { get; set; }
@@ -54,6 +55,24 @@ namespace MediaBrowser.Controller.LiveTv
}
[IgnoreDataMember]
+ protected override bool SupportsIsInMixedFolderDetection
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ [IgnoreDataMember]
+ public override bool SupportsPlayedStatus
+ {
+ get
+ {
+ return Status == RecordingStatus.Completed && base.SupportsPlayedStatus;
+ }
+ }
+
+ [IgnoreDataMember]
public override LocationType LocationType
{
get
@@ -111,7 +130,7 @@ namespace MediaBrowser.Controller.LiveTv
public override bool CanDelete()
{
- return true;
+ return Status == RecordingStatus.Completed;
}
public override bool IsAuthorizedToDelete(User user)
diff --git a/MediaBrowser.Controller/LiveTv/ProgramInfo.cs b/MediaBrowser.Controller/LiveTv/ProgramInfo.cs
index ea5e6dbc6..d0377fbfd 100644
--- a/MediaBrowser.Controller/LiveTv/ProgramInfo.cs
+++ b/MediaBrowser.Controller/LiveTv/ProgramInfo.cs
@@ -107,6 +107,8 @@ namespace MediaBrowser.Controller.LiveTv
/// <value>The image URL.</value>
public string ImageUrl { get; set; }
+ public string LogoImageUrl { get; set; }
+
/// <summary>
/// Gets or sets a value indicating whether this instance has image.
/// </summary>
diff --git a/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs b/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs
index 2d79473f0..5c73ed833 100644
--- a/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs
+++ b/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using MediaBrowser.Model.LiveTv;
namespace MediaBrowser.Controller.LiveTv
{
@@ -26,6 +27,8 @@ namespace MediaBrowser.Controller.LiveTv
/// </summary>
public string Name { get; set; }
+ public string ServiceName { get; set; }
+
/// <summary>
/// Description of the recording.
/// </summary>
@@ -53,6 +56,11 @@ namespace MediaBrowser.Controller.LiveTv
/// <value><c>true</c> if [record any channel]; otherwise, <c>false</c>.</value>
public bool RecordAnyChannel { get; set; }
+ public int KeepUpTo { get; set; }
+ public KeepUntil KeepUntil { get; set; }
+
+ public bool SkipEpisodesInLibrary { get; set; }
+
/// <summary>
/// Gets or sets a value indicating whether [record new only].
/// </summary>
@@ -104,6 +112,8 @@ namespace MediaBrowser.Controller.LiveTv
public SeriesTimerInfo()
{
Days = new List<DayOfWeek>();
+ SkipEpisodesInLibrary = true;
+ KeepUntil = KeepUntil.UntilDeleted;
}
}
}
diff --git a/MediaBrowser.Controller/LiveTv/TimerInfo.cs b/MediaBrowser.Controller/LiveTv/TimerInfo.cs
index 5d92a212f..fd614253a 100644
--- a/MediaBrowser.Controller/LiveTv/TimerInfo.cs
+++ b/MediaBrowser.Controller/LiveTv/TimerInfo.cs
@@ -1,10 +1,17 @@
using MediaBrowser.Model.LiveTv;
using System;
+using System.Collections.Generic;
namespace MediaBrowser.Controller.LiveTv
{
public class TimerInfo
{
+ public TimerInfo()
+ {
+ Genres = new List<string>();
+ KeepUntil = KeepUntil.UntilDeleted;
+ }
+
/// <summary>
/// Id of the recording.
/// </summary>
@@ -15,7 +22,7 @@ namespace MediaBrowser.Controller.LiveTv
/// </summary>
/// <value>The series timer identifier.</value>
public string SeriesTimerId { get; set; }
-
+
/// <summary>
/// ChannelId of the recording.
/// </summary>
@@ -26,7 +33,7 @@ namespace MediaBrowser.Controller.LiveTv
/// </summary>
/// <value>The program identifier.</value>
public string ProgramId { get; set; }
-
+
/// <summary>
/// Name of the recording.
/// </summary>
@@ -76,11 +83,36 @@ namespace MediaBrowser.Controller.LiveTv
/// </summary>
/// <value><c>true</c> if this instance is post padding required; otherwise, <c>false</c>.</value>
public bool IsPostPaddingRequired { get; set; }
-
+
/// <summary>
/// Gets or sets the priority.
/// </summary>
/// <value>The priority.</value>
public int Priority { get; set; }
+
+
+ // Program properties
+ public int? SeasonNumber { get; set; }
+ /// <summary>
+ /// Gets or sets the episode number.
+ /// </summary>
+ /// <value>The episode number.</value>
+ public int? EpisodeNumber { get; set; }
+ public bool IsMovie { get; set; }
+ public bool IsKids { get; set; }
+ public bool IsSports { get; set; }
+ public bool IsNews { get; set; }
+ public int? ProductionYear { get; set; }
+ public string EpisodeTitle { get; set; }
+ public DateTime? OriginalAirDate { get; set; }
+ public bool IsProgramSeries { get; set; }
+ public bool IsRepeat { get; set; }
+ public string HomePageUrl { get; set; }
+ public float? CommunityRating { get; set; }
+ public string ShortOverview { get; set; }
+ public string OfficialRating { get; set; }
+ public List<string> Genres { get; set; }
+ public string RecordingPath { get; set; }
+ public KeepUntil KeepUntil { get; set; }
}
}
diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
index 7cfd56c1e..11ed0f674 100644
--- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj
+++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
@@ -91,10 +91,7 @@
<Compile Include="Channels\InternalChannelItemQuery.cs" />
<Compile Include="Channels\IRequiresMediaInfoCallback.cs" />
<Compile Include="Channels\ISearchableChannel.cs" />
- <Compile Include="Chapters\ChapterSearchRequest.cs" />
<Compile Include="Chapters\IChapterManager.cs" />
- <Compile Include="Chapters\IChapterProvider.cs" />
- <Compile Include="Chapters\ChapterResponse.cs" />
<Compile Include="Collections\CollectionCreationOptions.cs" />
<Compile Include="Collections\CollectionEvents.cs" />
<Compile Include="Collections\ICollectionManager.cs" />
@@ -135,7 +132,6 @@
<Compile Include="Entities\Game.cs" />
<Compile Include="Entities\GameGenre.cs" />
<Compile Include="Entities\GameSystem.cs" />
- <Compile Include="Entities\IArchivable.cs" />
<Compile Include="Entities\IByReferenceItem.cs" />
<Compile Include="Entities\IHasAspectRatio.cs" />
<Compile Include="Entities\IHasBudget.cs" />
@@ -147,15 +143,12 @@
<Compile Include="Entities\IHasMediaSources.cs" />
<Compile Include="Entities\IHasMetascore.cs" />
<Compile Include="Entities\IHasOriginalTitle.cs" />
- <Compile Include="Entities\IHasProductionLocations.cs" />
<Compile Include="Entities\IHasProgramAttributes.cs" />
<Compile Include="Entities\IHasScreenshots.cs" />
<Compile Include="Entities\IHasSeries.cs" />
<Compile Include="Entities\IHasShortOverview.cs" />
<Compile Include="Entities\IHasSpecialFeatures.cs" />
<Compile Include="Entities\IHasStartDate.cs" />
- <Compile Include="Entities\IHasTaglines.cs" />
- <Compile Include="Entities\IHasThemeMedia.cs" />
<Compile Include="Entities\IHasTrailers.cs" />
<Compile Include="Entities\IHasUserData.cs" />
<Compile Include="Entities\IHiddenFromDisplay.cs" />
@@ -201,6 +194,7 @@
<Compile Include="Library\UserDataSaveEventArgs.cs" />
<Compile Include="LiveTv\IListingsProvider.cs" />
<Compile Include="LiveTv\ITunerHost.cs" />
+ <Compile Include="LiveTv\LiveStream.cs" />
<Compile Include="LiveTv\RecordingGroup.cs" />
<Compile Include="LiveTv\RecordingStatusChangedEventArgs.cs" />
<Compile Include="LiveTv\ILiveTvRecording.cs" />
@@ -266,7 +260,6 @@
<Compile Include="Playlists\IPlaylistManager.cs" />
<Compile Include="Playlists\Playlist.cs" />
<Compile Include="Plugins\ILocalizablePlugin.cs" />
- <Compile Include="Power\IPowerManagement.cs" />
<Compile Include="Providers\AlbumInfo.cs" />
<Compile Include="Providers\ArtistInfo.cs" />
<Compile Include="Providers\BookInfo.cs" />
@@ -290,9 +283,7 @@
<Compile Include="Providers\IHasItemChangeMonitor.cs" />
<Compile Include="Providers\IHasLookupInfo.cs" />
<Compile Include="Providers\IHasOrder.cs" />
- <Compile Include="Providers\IImageFileSaver.cs" />
<Compile Include="Providers\IImageProvider.cs" />
- <Compile Include="Providers\IImageSaver.cs" />
<Compile Include="Providers\ILocalImageFileProvider.cs" />
<Compile Include="Providers\ILocalMetadataProvider.cs" />
<Compile Include="Providers\ImageRefreshMode.cs" />
@@ -319,7 +310,6 @@
<Compile Include="Providers\SongInfo.cs" />
<Compile Include="Providers\TrailerInfo.cs" />
<Compile Include="Providers\VideoContentType.cs" />
- <Compile Include="RelatedMedia\IRelatedMediaProvider.cs" />
<Compile Include="Security\AuthenticationInfo.cs" />
<Compile Include="Security\AuthenticationInfoQuery.cs" />
<Compile Include="Security\IAuthenticationRepository.cs" />
diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
index 7fdbf020c..866a08aa1 100644
--- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
+++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
@@ -45,9 +45,9 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <param name="offset">The offset.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{Stream}.</returns>
- Task<string> ExtractVideoImage(string[] inputFiles, MediaProtocol protocol, Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken);
+ Task<string> ExtractVideoImage(string[] inputFiles, string container, MediaProtocol protocol, Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken);
- Task<string> ExtractVideoImage(string[] inputFiles, MediaProtocol protocol, int? imageStreamIndex, CancellationToken cancellationToken);
+ Task<string> ExtractVideoImage(string[] inputFiles, string container, MediaProtocol protocol, int? imageStreamIndex, CancellationToken cancellationToken);
/// <summary>
/// Extracts the video images on interval.
@@ -84,7 +84,7 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <param name="inputFiles">The input files.</param>
/// <param name="protocol">The protocol.</param>
/// <returns>System.String.</returns>
- string GetProbeSizeArgument(string[] inputFiles, MediaProtocol protocol);
+ string GetProbeSizeAndAnalyzeDurationArgument(string[] inputFiles, MediaProtocol protocol);
/// <summary>
/// Gets the input argument.
diff --git a/MediaBrowser.Controller/MediaEncoding/MediaInfoRequest.cs b/MediaBrowser.Controller/MediaEncoding/MediaInfoRequest.cs
index ca0c2fdbb..9ff7567d4 100644
--- a/MediaBrowser.Controller/MediaEncoding/MediaInfoRequest.cs
+++ b/MediaBrowser.Controller/MediaEncoding/MediaInfoRequest.cs
@@ -15,6 +15,7 @@ namespace MediaBrowser.Controller.MediaEncoding
public IIsoMount MountedIso { get; set; }
public VideoType VideoType { get; set; }
public List<string> PlayableStreamFileNames { get; set; }
+ public int AnalyzeDurationSections { get; set; }
public MediaInfoRequest()
{
diff --git a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs
index 3756f1d93..be177cb02 100644
--- a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs
+++ b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs
@@ -120,7 +120,7 @@ namespace MediaBrowser.Controller.Net
var cancellationTokenSource = new CancellationTokenSource();
- Logger.Info("{1} Begin transmitting over websocket to {0}", message.Connection.RemoteEndPoint, GetType().Name);
+ Logger.Debug("{1} Begin transmitting over websocket to {0}", message.Connection.RemoteEndPoint, GetType().Name);
var timer = SendOnTimer ?
new Timer(TimerCallback, message.Connection, Timeout.Infinite, Timeout.Infinite) :
@@ -267,7 +267,7 @@ namespace MediaBrowser.Controller.Net
/// <param name="connection">The connection.</param>
private void DisposeConnection(Tuple<IWebSocketConnection, CancellationTokenSource, Timer, TStateType, SemaphoreSlim> connection)
{
- Logger.Info("{1} stop transmitting over websocket to {0}", connection.Item1.RemoteEndPoint, GetType().Name);
+ Logger.Debug("{1} stop transmitting over websocket to {0}", connection.Item1.RemoteEndPoint, GetType().Name);
var timer = connection.Item3;
diff --git a/MediaBrowser.Controller/Net/IHttpResultFactory.cs b/MediaBrowser.Controller/Net/IHttpResultFactory.cs
index 8fdb1ce37..ca453840f 100644
--- a/MediaBrowser.Controller/Net/IHttpResultFactory.cs
+++ b/MediaBrowser.Controller/Net/IHttpResultFactory.cs
@@ -12,14 +12,6 @@ namespace MediaBrowser.Controller.Net
public interface IHttpResultFactory
{
/// <summary>
- /// Throws the error.
- /// </summary>
- /// <param name="statusCode">The status code.</param>
- /// <param name="errorMessage">The error message.</param>
- /// <param name="responseHeaders">The response headers.</param>
- void ThrowError(int statusCode, string errorMessage, IDictionary<string, string> responseHeaders = null);
-
- /// <summary>
/// Gets the result.
/// </summary>
/// <param name="content">The content.</param>
diff --git a/MediaBrowser.Controller/Playlists/Playlist.cs b/MediaBrowser.Controller/Playlists/Playlist.cs
index 654b97d7d..6ab85f079 100644
--- a/MediaBrowser.Controller/Playlists/Playlist.cs
+++ b/MediaBrowser.Controller/Playlists/Playlist.cs
@@ -32,6 +32,15 @@ namespace MediaBrowser.Controller.Playlists
}
[IgnoreDataMember]
+ public override bool SupportsPlayedStatus
+ {
+ get
+ {
+ return string.Equals(MediaType, "Video", StringComparison.OrdinalIgnoreCase);
+ }
+ }
+
+ [IgnoreDataMember]
public override bool AlwaysScanInternalMetadataPath
{
get
@@ -198,15 +207,15 @@ namespace MediaBrowser.Controller.Playlists
public override bool IsVisible(User user)
{
- if (base.IsVisible(user))
- {
- var userId = user.Id.ToString("N");
+ var userId = user.Id.ToString("N");
- return Shares.Any(i => string.Equals(userId, i.UserId, StringComparison.OrdinalIgnoreCase)) ||
- string.Equals(OwnerUserId, userId, StringComparison.OrdinalIgnoreCase);
- }
+ return Shares.Any(i => string.Equals(userId, i.UserId, StringComparison.OrdinalIgnoreCase)) ||
+ string.Equals(OwnerUserId, userId, StringComparison.OrdinalIgnoreCase);
+ }
- return false;
+ public override bool IsVisibleStandalone(User user)
+ {
+ return IsVisible(user);
}
}
}
diff --git a/MediaBrowser.Controller/Power/IPowerManagement.cs b/MediaBrowser.Controller/Power/IPowerManagement.cs
deleted file mode 100644
index faa289695..000000000
--- a/MediaBrowser.Controller/Power/IPowerManagement.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using System;
-
-namespace MediaBrowser.Controller.Power
-{
- public interface IPowerManagement
- {
- /// <summary>
- /// Schedules the wake.
- /// </summary>
- /// <param name="utcTime">The UTC time.</param>
- void ScheduleWake(DateTime utcTime);
- }
-}
diff --git a/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs
index fccbd9211..13d43eee6 100644
--- a/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs
+++ b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs
@@ -347,7 +347,7 @@ namespace MediaBrowser.Controller.Providers
var person = item as Person;
if (person != null)
{
- person.PlaceOfBirth = val;
+ person.ProductionLocations = new List<string> { val };
}
}
@@ -790,7 +790,7 @@ namespace MediaBrowser.Controller.Providers
}
default:
- {
+ {
string readerName = reader.Name;
string providerIdValue;
if (_validProviderIds.TryGetValue(readerName, out providerIdValue))
@@ -893,14 +893,6 @@ namespace MediaBrowser.Controller.Providers
if (!string.IsNullOrWhiteSpace(val))
{
- var hasProductionLocations = item as IHasProductionLocations;
- if (hasProductionLocations != null)
- {
- if (!string.IsNullOrWhiteSpace(val))
- {
- hasProductionLocations.AddProductionLocation(val);
- }
- }
}
break;
}
@@ -934,14 +926,7 @@ namespace MediaBrowser.Controller.Providers
if (!string.IsNullOrWhiteSpace(val))
{
- var hasTaglines = item as IHasTaglines;
- if (hasTaglines != null)
- {
- if (!string.IsNullOrWhiteSpace(val))
- {
- hasTaglines.AddTagline(val);
- }
- }
+ item.Tagline = val;
}
break;
}
diff --git a/MediaBrowser.Controller/Providers/IImageFileSaver.cs b/MediaBrowser.Controller/Providers/IImageFileSaver.cs
deleted file mode 100644
index 3e11d8bf8..000000000
--- a/MediaBrowser.Controller/Providers/IImageFileSaver.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using System.Collections.Generic;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Model.Drawing;
-using MediaBrowser.Model.Entities;
-
-namespace MediaBrowser.Controller.Providers
-{
- public interface IImageFileSaver : IImageSaver
- {
- /// <summary>
- /// Gets the save paths.
- /// </summary>
- /// <param name="item">The item.</param>
- /// <param name="type">The type.</param>
- /// <param name="format">The format.</param>
- /// <param name="index">The index.</param>
- /// <returns>IEnumerable{System.String}.</returns>
- IEnumerable<string> GetSavePaths(IHasImages item, ImageType type, ImageFormat format, int index);
- }
-} \ No newline at end of file
diff --git a/MediaBrowser.Controller/Providers/IImageSaver.cs b/MediaBrowser.Controller/Providers/IImageSaver.cs
deleted file mode 100644
index 62017160f..000000000
--- a/MediaBrowser.Controller/Providers/IImageSaver.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-namespace MediaBrowser.Controller.Providers
-{
- public interface IImageSaver
- {
- /// <summary>
- /// Gets the name.
- /// </summary>
- /// <value>The name.</value>
- string Name { get; }
- }
-}
diff --git a/MediaBrowser.Controller/Providers/IProviderManager.cs b/MediaBrowser.Controller/Providers/IProviderManager.cs
index 3eefa9647..d3e5685bb 100644
--- a/MediaBrowser.Controller/Providers/IProviderManager.cs
+++ b/MediaBrowser.Controller/Providers/IProviderManager.cs
@@ -95,15 +95,8 @@ namespace MediaBrowser.Controller.Providers
/// <summary>
/// Adds the metadata providers.
/// </summary>
- /// <param name="imageProviders">The image providers.</param>
- /// <param name="metadataServices">The metadata services.</param>
- /// <param name="metadataProviders">The metadata providers.</param>
- /// <param name="savers">The savers.</param>
- /// <param name="imageSavers">The image savers.</param>
- /// <param name="externalIds">The external ids.</param>
void AddParts(IEnumerable<IImageProvider> imageProviders, IEnumerable<IMetadataService> metadataServices, IEnumerable<IMetadataProvider> metadataProviders,
IEnumerable<IMetadataSaver> savers,
- IEnumerable<IImageSaver> imageSavers,
IEnumerable<IExternalId> externalIds);
/// <summary>
diff --git a/MediaBrowser.Controller/Providers/ItemInfo.cs b/MediaBrowser.Controller/Providers/ItemInfo.cs
index 63cc48058..8de11b743 100644
--- a/MediaBrowser.Controller/Providers/ItemInfo.cs
+++ b/MediaBrowser.Controller/Providers/ItemInfo.cs
@@ -10,7 +10,7 @@ namespace MediaBrowser.Controller.Providers
{
Path = item.Path;
ContainingFolderPath = item.ContainingFolderPath;
- IsInMixedFolder = item.IsInMixedFolder;
+ IsInMixedFolder = item.DetectIsInMixedFolder();
var video = item as Video;
if (video != null)
diff --git a/MediaBrowser.Controller/Providers/MetadataResult.cs b/MediaBrowser.Controller/Providers/MetadataResult.cs
index 17175f91c..99402a969 100644
--- a/MediaBrowser.Controller/Providers/MetadataResult.cs
+++ b/MediaBrowser.Controller/Providers/MetadataResult.cs
@@ -13,13 +13,15 @@ namespace MediaBrowser.Controller.Providers
public MetadataResult()
{
Images = new List<LocalImageInfo>();
+ ResultLanguage = "en";
}
public List<PersonInfo> People { get; set; }
public bool HasMetadata { get; set; }
public T Item { get; set; }
-
+ public string ResultLanguage { get; set; }
+ public bool QueriedById { get; set; }
public void AddPerson(PersonInfo p)
{
if (People == null)
diff --git a/MediaBrowser.Controller/RelatedMedia/IRelatedMediaProvider.cs b/MediaBrowser.Controller/RelatedMedia/IRelatedMediaProvider.cs
deleted file mode 100644
index bb2a0cd89..000000000
--- a/MediaBrowser.Controller/RelatedMedia/IRelatedMediaProvider.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-namespace MediaBrowser.Controller.RelatedMedia
-{
- public interface IRelatedMediaProvider
- {
- /// <summary>
- /// Gets the name.
- /// </summary>
- /// <value>The name.</value>
- string Name { get; }
- }
-}
diff --git a/MediaBrowser.Dlna/ContentDirectory/ContentDirectoryBrowser.cs b/MediaBrowser.Dlna/ContentDirectory/ContentDirectoryBrowser.cs
index 77b0a70fd..261887366 100644
--- a/MediaBrowser.Dlna/ContentDirectory/ContentDirectoryBrowser.cs
+++ b/MediaBrowser.Dlna/ContentDirectory/ContentDirectoryBrowser.cs
@@ -34,7 +34,8 @@ namespace MediaBrowser.Dlna.ContentDirectory
UserAgent = "Emby",
RequestContentType = "text/xml; charset=\"utf-8\"",
LogErrorResponseBody = true,
- Url = request.ContentDirectoryUrl
+ Url = request.ContentDirectoryUrl,
+ BufferContent = false
};
options.RequestHeaders["SOAPACTION"] = "urn:schemas-upnp-org:service:ContentDirectory:1#Browse";
diff --git a/MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs b/MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs
index 9b5030325..3d1e073f8 100644
--- a/MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs
+++ b/MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs
@@ -428,14 +428,6 @@ namespace MediaBrowser.Dlna.ContentDirectory
return ApplyPaging(result, startIndex, limit);
}
- if (stubType.Value == StubType.Folder)
- {
- var movie = item as Movie;
- if (movie != null)
- {
- return ApplyPaging(await GetMovieItems(movie).ConfigureAwait(false), startIndex, limit);
- }
- }
var person = item as Person;
if (person != null)
@@ -468,14 +460,11 @@ namespace MediaBrowser.Dlna.ContentDirectory
}).ConfigureAwait(false);
- var options = _config.GetDlnaConfiguration();
-
var serverItems = queryResult
.Items
.Select(i => new ServerItem
{
- Item = i,
- StubType = GetDisplayStubType(i, item, options)
+ Item = i
})
.ToArray();
@@ -519,29 +508,6 @@ namespace MediaBrowser.Dlna.ContentDirectory
return result;
}
- private StubType? GetDisplayStubType(BaseItem item, BaseItem context, DlnaOptions options)
- {
- if (context == null || context.IsFolder)
- {
- var movie = item as Movie;
- if (movie != null && options.EnableMovieFolders)
- {
- if (movie.GetTrailerIds().Count > 0 ||
- movie.SpecialFeatureIds.Count > 0)
- {
- return StubType.Folder;
- }
-
- if (EnablePeopleDisplay(item))
- {
- return StubType.Folder;
- }
- }
- }
-
- return null;
- }
-
private bool EnablePeopleDisplay(BaseItem item)
{
if (_libraryManager.GetPeopleNames(new InternalPeopleQuery
@@ -556,31 +522,6 @@ namespace MediaBrowser.Dlna.ContentDirectory
return false;
}
- private Task<QueryResult<ServerItem>> GetMovieItems(Movie item)
- {
- var list = new List<BaseItem>();
-
- list.Add(item);
-
- list.AddRange(item.GetTrailerIds().Select(i => _libraryManager.GetItemById(i)).Where(i => i != null));
- list.AddRange(item.SpecialFeatureIds.Select(i => _libraryManager.GetItemById(i)).Where(i => i != null));
-
- var serverItems = list.Select(i => new ServerItem { Item = i, StubType = null })
- .ToList();
-
- serverItems.Add(new ServerItem
- {
- Item = item,
- StubType = StubType.People
- });
-
- return Task.FromResult(new QueryResult<ServerItem>
- {
- Items = serverItems.ToArray(),
- TotalRecordCount = serverItems.Count
- });
- }
-
private ServerItem GetItemFromObjectId(string id, User user)
{
return DidlBuilder.IsIdRoot(id)
diff --git a/MediaBrowser.Dlna/Didl/DidlBuilder.cs b/MediaBrowser.Dlna/Didl/DidlBuilder.cs
index 84c1b3bde..f53dec3bf 100644
--- a/MediaBrowser.Dlna/Didl/DidlBuilder.cs
+++ b/MediaBrowser.Dlna/Didl/DidlBuilder.cs
@@ -177,7 +177,8 @@ namespace MediaBrowser.Dlna.Didl
streamInfo.TargetRefFrames,
streamInfo.TargetVideoStreamCount,
streamInfo.TargetAudioStreamCount,
- streamInfo.TargetVideoCodecTag);
+ streamInfo.TargetVideoCodecTag,
+ streamInfo.IsTargetAVC);
foreach (var contentFeature in contentFeatureList)
{
@@ -322,7 +323,8 @@ namespace MediaBrowser.Dlna.Didl
streamInfo.TargetRefFrames,
streamInfo.TargetVideoStreamCount,
streamInfo.TargetAudioStreamCount,
- streamInfo.TargetVideoCodecTag);
+ streamInfo.TargetVideoCodecTag,
+ streamInfo.IsTargetAVC);
var filename = url.Substring(0, url.IndexOf('?'));
diff --git a/MediaBrowser.Dlna/Eventing/EventManager.cs b/MediaBrowser.Dlna/Eventing/EventManager.cs
index 65e742d49..51c8d2d91 100644
--- a/MediaBrowser.Dlna/Eventing/EventManager.cs
+++ b/MediaBrowser.Dlna/Eventing/EventManager.cs
@@ -141,7 +141,8 @@ namespace MediaBrowser.Dlna.Eventing
{
RequestContent = builder.ToString(),
RequestContentType = "text/xml",
- Url = subscription.CallbackUrl
+ Url = subscription.CallbackUrl,
+ BufferContent = false
};
options.RequestHeaders.Add("NT", subscription.NotificationType);
@@ -155,7 +156,6 @@ namespace MediaBrowser.Dlna.Eventing
}
catch (OperationCanceledException)
{
- throw;
}
catch
{
diff --git a/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs b/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs
index 9f2726b31..a8aedaed8 100644
--- a/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs
+++ b/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs
@@ -14,8 +14,11 @@ using MediaBrowser.Dlna.Ssdp;
using MediaBrowser.Model.Logging;
using System;
using System.Collections.Generic;
+using System.Linq;
+using System.Net;
using System.Threading.Tasks;
using MediaBrowser.Controller.MediaEncoding;
+using Rssdp;
namespace MediaBrowser.Dlna.Main
{
@@ -38,12 +41,11 @@ namespace MediaBrowser.Dlna.Main
private readonly IMediaSourceManager _mediaSourceManager;
private readonly IMediaEncoder _mediaEncoder;
- private readonly SsdpHandler _ssdpHandler;
private readonly IDeviceDiscovery _deviceDiscovery;
- private readonly List<string> _registeredServerIds = new List<string>();
private bool _ssdpHandlerStarted;
private bool _dlnaServerStarted;
+ private SsdpDevicePublisher _Publisher;
public DlnaEntryPoint(IServerConfigurationManager config,
ILogManager logManager,
@@ -58,7 +60,7 @@ namespace MediaBrowser.Dlna.Main
IUserDataManager userDataManager,
ILocalizationManager localization,
IMediaSourceManager mediaSourceManager,
- ISsdpHandler ssdpHandler, IDeviceDiscovery deviceDiscovery, IMediaEncoder mediaEncoder)
+ IDeviceDiscovery deviceDiscovery, IMediaEncoder mediaEncoder)
{
_config = config;
_appHost = appHost;
@@ -74,7 +76,6 @@ namespace MediaBrowser.Dlna.Main
_mediaSourceManager = mediaSourceManager;
_deviceDiscovery = deviceDiscovery;
_mediaEncoder = mediaEncoder;
- _ssdpHandler = (SsdpHandler)ssdpHandler;
_logger = logManager.GetLogger("Dlna");
}
@@ -110,18 +111,6 @@ namespace MediaBrowser.Dlna.Main
{
var options = _config.GetDlnaConfiguration();
- if (!options.EnableServer && !options.EnablePlayTo && !_config.Configuration.EnableUPnP)
- {
- if (_ssdpHandlerStarted)
- {
- // Sat/ip live tv depends on device discovery, as well as hd homerun detection
- // In order to allow this to be disabled, we need a modular way of knowing if there are
- // any parts of the system that are dependant on it
- // DisposeSsdpHandler();
- }
- return;
- }
-
if (!_ssdpHandlerStarted)
{
StartSsdpHandler();
@@ -154,7 +143,7 @@ namespace MediaBrowser.Dlna.Main
{
try
{
- _ssdpHandler.Start();
+ StartPublishing();
_ssdpHandlerStarted = true;
StartDeviceDiscovery();
@@ -165,13 +154,16 @@ namespace MediaBrowser.Dlna.Main
}
}
+ private void StartPublishing()
+ {
+ _Publisher = new SsdpDevicePublisher();
+ }
+
private void StartDeviceDiscovery()
{
try
{
- ((DeviceDiscovery)_deviceDiscovery).Start(_ssdpHandler);
-
- //DlnaChannel.Current.Start(() => _registeredServerIds.ToList());
+ ((DeviceDiscovery)_deviceDiscovery).Start();
}
catch (Exception ex)
{
@@ -199,8 +191,6 @@ namespace MediaBrowser.Dlna.Main
{
((DeviceDiscovery)_deviceDiscovery).Dispose();
- _ssdpHandler.Dispose();
-
_ssdpHandlerStarted = false;
}
catch (Exception ex)
@@ -225,7 +215,17 @@ namespace MediaBrowser.Dlna.Main
private async Task RegisterServerEndpoints()
{
- foreach (var address in await _appHost.GetLocalIpAddresses().ConfigureAwait(false))
+ if (!_config.GetDlnaConfiguration().BlastAliveMessages)
+ {
+ return;
+ }
+
+ var cacheLength = _config.GetDlnaConfiguration().BlastAliveMessageIntervalSeconds;
+ _Publisher.SupportPnpRootDevice = false;
+
+ var addresses = (await _appHost.GetLocalIpAddresses().ConfigureAwait(false)).ToList();
+
+ foreach (var address in addresses)
{
//if (IPAddress.IsLoopback(address))
//{
@@ -234,25 +234,46 @@ namespace MediaBrowser.Dlna.Main
//}
var addressString = address.ToString();
- var udn = addressString.GetMD5().ToString("N");
-
- var descriptorURI = "/dlna/" + udn + "/description.xml";
- var uri = new Uri(_appHost.GetLocalApiUrl(address) + descriptorURI);
+ var udn = (addressString).GetMD5().ToString("N");
var services = new List<string>
{
- "upnp:rootdevice",
"urn:schemas-upnp-org:device:MediaServer:1",
"urn:schemas-upnp-org:service:ContentDirectory:1",
"urn:schemas-upnp-org:service:ConnectionManager:1",
- "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1",
- "uuid:" + udn
+ "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1"
};
- _ssdpHandler.RegisterNotification(udn, uri, address, services);
+ foreach (var fullService in services)
+ {
+ _logger.Info("Registering publisher for {0} on {1}", fullService, addressString);
+
+ var descriptorURI = "/dlna/" + udn + "/description.xml";
+ var uri = new Uri(_appHost.GetLocalApiUrl(address) + descriptorURI);
+
+ var service = fullService.Replace("urn:", string.Empty).Replace(":1", string.Empty);
+
+ var serviceParts = service.Split(':');
+
+ var deviceTypeNamespace = serviceParts[0].Replace('.', '-');
- _registeredServerIds.Add(udn);
+ var device = new SsdpRootDevice
+ {
+ CacheLifetime = TimeSpan.FromSeconds(cacheLength), //How long SSDP clients can cache this info.
+ Location = uri, // Must point to the URL that serves your devices UPnP description document.
+ DeviceTypeNamespace = deviceTypeNamespace,
+ DeviceClass = serviceParts[1],
+ DeviceType = serviceParts[2],
+ FriendlyName = "Emby Server",
+ Manufacturer = "Emby",
+ ModelName = "Emby Server",
+ Uuid = udn
+ // This must be a globally unique value that survives reboots etc. Get from storage or embedded hardware etc.
+ };
+
+ _Publisher.AddDevice(device);
+ }
}
}
@@ -315,19 +336,25 @@ namespace MediaBrowser.Dlna.Main
public void DisposeDlnaServer()
{
- foreach (var id in _registeredServerIds)
+ if (_Publisher != null)
{
- try
- {
- _ssdpHandler.UnregisterNotification(id);
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error unregistering server", ex);
- }
- }
+ var devices = _Publisher.Devices.ToList();
+ var tasks = devices.Select(i => _Publisher.RemoveDevice(i)).ToArray();
- _registeredServerIds.Clear();
+ Task.WaitAll(tasks);
+ //foreach (var device in devices)
+ //{
+ // try
+ // {
+ // _Publisher.RemoveDevice(device);
+ // }
+ // catch (Exception ex)
+ // {
+ // _logger.ErrorException("Error sending bye bye", ex);
+ // }
+ //}
+ _Publisher.Dispose();
+ }
_dlnaServerStarted = false;
}
diff --git a/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj b/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj
index d10a5f7b5..ef60be227 100644
--- a/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj
+++ b/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@@ -14,6 +14,7 @@
<SchemaVersion>2.0</SchemaVersion>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
+ <TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@@ -23,7 +24,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
- <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ <TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>none</DebugType>
@@ -32,7 +33,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
- <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ <TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release Mono|AnyCPU' ">
<Optimize>false</Optimize>
@@ -50,8 +51,15 @@
<Reference Include="Patterns.Logging">
<HintPath>..\packages\Patterns.Logging.1.0.0.2\lib\portable-net45+sl4+wp71+win8+wpa81\Patterns.Logging.dll</HintPath>
</Reference>
+ <Reference Include="Rssdp.NetFx40">
+ <HintPath>..\ThirdParty\rssdp\Rssdp.NetFx40.dll</HintPath>
+ </Reference>
+ <Reference Include="Rssdp.Portable">
+ <HintPath>..\ThirdParty\rssdp\Rssdp.Portable.dll</HintPath>
+ </Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
+ <Reference Include="System.Net.Http" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
@@ -125,7 +133,6 @@
<Compile Include="Ssdp\Datagram.cs" />
<Compile Include="Server\DescriptionXmlBuilder.cs" />
<Compile Include="Ssdp\DeviceDiscovery.cs" />
- <Compile Include="Ssdp\SsdpHelper.cs" />
<Compile Include="PlayTo\SsdpHttpClient.cs" />
<Compile Include="Common\StateVariable.cs" />
<Compile Include="PlayTo\TransportCommands.cs" />
diff --git a/MediaBrowser.Dlna/PlayTo/Device.cs b/MediaBrowser.Dlna/PlayTo/Device.cs
index d1802b3ad..b656bc66e 100644
--- a/MediaBrowser.Dlna/PlayTo/Device.cs
+++ b/MediaBrowser.Dlna/PlayTo/Device.cs
@@ -474,7 +474,7 @@ namespace MediaBrowser.Dlna.PlayTo
if (_disposed)
return;
- _logger.ErrorException("Error updating device info for {0}", ex, Properties.Name);
+ //_logger.ErrorException("Error updating device info for {0}", ex, Properties.Name);
_successiveStopCount++;
_connectFailureCount++;
diff --git a/MediaBrowser.Dlna/PlayTo/PlayToController.cs b/MediaBrowser.Dlna/PlayTo/PlayToController.cs
index 5622885fc..f6b24e615 100644
--- a/MediaBrowser.Dlna/PlayTo/PlayToController.cs
+++ b/MediaBrowser.Dlna/PlayTo/PlayToController.cs
@@ -19,6 +19,7 @@ using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Model.Events;
namespace MediaBrowser.Dlna.PlayTo
{
@@ -122,16 +123,18 @@ namespace MediaBrowser.Dlna.PlayTo
}
}
- void _deviceDiscovery_DeviceLeft(object sender, SsdpMessageEventArgs e)
+ void _deviceDiscovery_DeviceLeft(object sender, GenericEventArgs<UpnpDeviceInfo> e)
{
+ var info = e.Argument;
+
string nts;
- e.Headers.TryGetValue("NTS", out nts);
+ info.Headers.TryGetValue("NTS", out nts);
string usn;
- if (!e.Headers.TryGetValue("USN", out usn)) usn = String.Empty;
+ if (!info.Headers.TryGetValue("USN", out usn)) usn = String.Empty;
string nt;
- if (!e.Headers.TryGetValue("NT", out nt)) nt = String.Empty;
+ if (!info.Headers.TryGetValue("NT", out nt)) nt = String.Empty;
if (usn.IndexOf(_device.Properties.UUID, StringComparison.OrdinalIgnoreCase) != -1 &&
!_disposed)
@@ -537,7 +540,8 @@ namespace MediaBrowser.Dlna.PlayTo
streamInfo.TargetRefFrames,
streamInfo.TargetVideoStreamCount,
streamInfo.TargetAudioStreamCount,
- streamInfo.TargetVideoCodecTag);
+ streamInfo.TargetVideoCodecTag,
+ streamInfo.IsTargetAVC);
return list.FirstOrDefault();
}
@@ -653,7 +657,7 @@ namespace MediaBrowser.Dlna.PlayTo
_device.PlaybackProgress -= _device_PlaybackProgress;
_device.PlaybackStopped -= _device_PlaybackStopped;
_device.MediaChanged -= _device_MediaChanged;
- _deviceDiscovery.DeviceLeft -= _deviceDiscovery_DeviceLeft;
+ //_deviceDiscovery.DeviceLeft -= _deviceDiscovery_DeviceLeft;
_device.OnDeviceUnavailable = null;
_device.Dispose();
@@ -824,6 +828,7 @@ namespace MediaBrowser.Dlna.PlayTo
public string DeviceId { get; set; }
public string MediaSourceId { get; set; }
+ public string LiveStreamId { get; set; }
public BaseItem Item { get; set; }
public MediaSourceInfo MediaSource { get; set; }
@@ -907,6 +912,10 @@ namespace MediaBrowser.Dlna.PlayTo
{
request.StartPositionTicks = long.Parse(val, CultureInfo.InvariantCulture);
}
+ else if (i == 22)
+ {
+ request.LiveStreamId = val;
+ }
}
request.Item = string.IsNullOrWhiteSpace(request.ItemId)
@@ -917,7 +926,7 @@ namespace MediaBrowser.Dlna.PlayTo
request.MediaSource = hasMediaSources == null
? null
- : (await mediaSourceManager.GetMediaSource(hasMediaSources, request.MediaSourceId, false).ConfigureAwait(false));
+ : (await mediaSourceManager.GetMediaSource(hasMediaSources, request.MediaSourceId, request.LiveStreamId, false, CancellationToken.None).ConfigureAwait(false));
return request;
}
diff --git a/MediaBrowser.Dlna/PlayTo/PlayToManager.cs b/MediaBrowser.Dlna/PlayTo/PlayToManager.cs
index cd9a7b1f0..6d6986f01 100644
--- a/MediaBrowser.Dlna/PlayTo/PlayToManager.cs
+++ b/MediaBrowser.Dlna/PlayTo/PlayToManager.cs
@@ -12,7 +12,9 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
+using System.Threading.Tasks;
using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Model.Events;
namespace MediaBrowser.Dlna.PlayTo
{
@@ -61,16 +63,17 @@ namespace MediaBrowser.Dlna.PlayTo
_deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered;
}
- async void _deviceDiscovery_DeviceDiscovered(object sender, SsdpMessageEventArgs e)
+ async void _deviceDiscovery_DeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
{
+ var info = e.Argument;
+
string usn;
- if (!e.Headers.TryGetValue("USN", out usn)) usn = string.Empty;
+ if (!info.Headers.TryGetValue("USN", out usn)) usn = string.Empty;
string nt;
- if (!e.Headers.TryGetValue("NT", out nt)) nt = string.Empty;
+ if (!info.Headers.TryGetValue("NT", out nt)) nt = string.Empty;
- string location;
- if (!e.Headers.TryGetValue("Location", out location)) location = string.Empty;
+ string location = info.Location.ToString();
// It has to report that it's a media renderer
if (usn.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) == -1 &&
@@ -100,7 +103,7 @@ namespace MediaBrowser.Dlna.PlayTo
}
}
- var uri = new Uri(location);
+ var uri = info.Location;
_logger.Debug("Attempting to create PlayToController from location {0}", location);
var device = await Device.CreateuPnpDeviceAsync(uri, _httpClient, _config, _logger).ConfigureAwait(false);
@@ -121,7 +124,7 @@ namespace MediaBrowser.Dlna.PlayTo
if (controller == null)
{
- var serverAddress = GetServerAddress(e.LocalEndPoint.Address);
+ var serverAddress = await GetServerAddress(info.LocalEndPoint == null ? null : info.LocalEndPoint.Address).ConfigureAwait(false);
string accessToken = null;
sessionInfo.SessionController = controller = new PlayToController(sessionInfo,
@@ -173,9 +176,14 @@ namespace MediaBrowser.Dlna.PlayTo
}
}
- private string GetServerAddress(IPAddress localIp)
+ private Task<string> GetServerAddress(IPAddress localIp)
{
- return _appHost.GetLocalApiUrl(localIp);
+ if (localIp == null)
+ {
+ return _appHost.GetLocalApiUrl();
+ }
+
+ return Task.FromResult(_appHost.GetLocalApiUrl(localIp));
}
public void Dispose()
diff --git a/MediaBrowser.Dlna/PlayTo/SsdpHttpClient.cs b/MediaBrowser.Dlna/PlayTo/SsdpHttpClient.cs
index ff4223c72..a1eb19d9a 100644
--- a/MediaBrowser.Dlna/PlayTo/SsdpHttpClient.cs
+++ b/MediaBrowser.Dlna/PlayTo/SsdpHttpClient.cs
@@ -70,7 +70,8 @@ namespace MediaBrowser.Dlna.PlayTo
{
Url = url,
UserAgent = USERAGENT,
- LogErrorResponseBody = true
+ LogErrorResponseBody = true,
+ BufferContent = false
};
options.RequestHeaders["HOST"] = ip + ":" + port.ToString(_usCulture);
@@ -87,7 +88,8 @@ namespace MediaBrowser.Dlna.PlayTo
{
Url = url,
UserAgent = USERAGENT,
- LogErrorResponseBody = true
+ LogErrorResponseBody = true,
+ BufferContent = false
};
options.RequestHeaders["FriendlyName.DLNA.ORG"] = FriendlyName;
@@ -115,7 +117,8 @@ namespace MediaBrowser.Dlna.PlayTo
Url = url,
UserAgent = USERAGENT,
LogRequest = logRequest || _config.GetDlnaConfiguration().EnableDebugLog,
- LogErrorResponseBody = true
+ LogErrorResponseBody = true,
+ BufferContent = false
};
options.RequestHeaders["SOAPAction"] = soapAction;
diff --git a/MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs b/MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs
index 68768745e..c9bba526a 100644
--- a/MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs
+++ b/MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs
@@ -11,6 +11,8 @@ using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Net;
+using MediaBrowser.Model.Events;
+using Rssdp;
namespace MediaBrowser.Dlna.Ssdp
{
@@ -20,211 +22,114 @@ namespace MediaBrowser.Dlna.Ssdp
private readonly ILogger _logger;
private readonly IServerConfigurationManager _config;
- private SsdpHandler _ssdpHandler;
private readonly CancellationTokenSource _tokenSource;
- private readonly IServerApplicationHost _appHost;
- public event EventHandler<SsdpMessageEventArgs> DeviceDiscovered;
- public event EventHandler<SsdpMessageEventArgs> DeviceLeft;
- private readonly INetworkManager _networkManager;
+ public event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceDiscovered;
+ public event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceLeft;
- public DeviceDiscovery(ILogger logger, IServerConfigurationManager config, IServerApplicationHost appHost, INetworkManager networkManager)
+ private SsdpDeviceLocator _DeviceLocator;
+
+ public DeviceDiscovery(ILogger logger, IServerConfigurationManager config)
{
_tokenSource = new CancellationTokenSource();
_logger = logger;
_config = config;
- _appHost = appHost;
- _networkManager = networkManager;
}
- private List<IPAddress> GetLocalIpAddresses()
- {
- return _networkManager.GetLocalIpAddresses().ToList();
- }
-
- public void Start(SsdpHandler ssdpHandler)
+ // Call this method from somewhere in your code to start the search.
+ public void BeginSearch()
{
- _ssdpHandler = ssdpHandler;
- _ssdpHandler.MessageReceived += _ssdpHandler_MessageReceived;
-
- foreach (var localIp in GetLocalIpAddresses())
- {
- try
- {
- CreateListener(localIp);
- }
- catch (Exception e)
- {
- _logger.ErrorException("Failed to Initilize Socket", e);
- }
- }
- }
+ _DeviceLocator = new SsdpDeviceLocator();
- async void _ssdpHandler_MessageReceived(object sender, SsdpMessageEventArgs e)
- {
- string nts;
- e.Headers.TryGetValue("NTS", out nts);
+ // (Optional) Set the filter so we only see notifications for devices we care about
+ // (can be any search target value i.e device type, uuid value etc - any value that appears in the
+ // DiscoverdSsdpDevice.NotificationType property or that is used with the searchTarget parameter of the Search method).
+ //_DeviceLocator.NotificationFilter = "upnp:rootdevice";
- if (String.Equals(e.Method, "NOTIFY", StringComparison.OrdinalIgnoreCase) &&
- String.Equals(nts, "ssdp:byebye", StringComparison.OrdinalIgnoreCase) &&
- !_disposed)
- {
- EventHelper.FireEventIfNotNull(DeviceLeft, this, e, _logger);
- return;
- }
+ // Connect our event handler so we process devices as they are found
+ _DeviceLocator.DeviceAvailable += deviceLocator_DeviceAvailable;
+ _DeviceLocator.DeviceUnavailable += _DeviceLocator_DeviceUnavailable;
- try
- {
- if (e.LocalEndPoint == null)
- {
- var ip = (await _appHost.GetLocalIpAddresses().ConfigureAwait(false)).FirstOrDefault(i => !IPAddress.IsLoopback(i));
- if (ip != null)
- {
- e.LocalEndPoint = new IPEndPoint(ip, 0);
- }
- }
-
- if (e.LocalEndPoint != null)
- {
- TryCreateDevice(e);
- }
- }
- catch (OperationCanceledException)
- {
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error creating play to controller", ex);
- }
+ // Perform a search so we don't have to wait for devices to broadcast notifications
+ // again to get any results right away (notifications are broadcast periodically).
+ StartAsyncSearch();
}
- private void CreateListener(IPAddress localIp)
+ private void StartAsyncSearch()
{
Task.Factory.StartNew(async (o) =>
{
- try
+ while (!_tokenSource.IsCancellationRequested)
{
- _logger.Info("Creating SSDP listener on {0}", localIp);
-
- var endPoint = new IPEndPoint(localIp, 1900);
-
- using (var socket = GetMulticastSocket(localIp, endPoint))
+ try
{
- var receiveBuffer = new byte[64000];
-
- CreateNotifier(localIp);
-
- while (!_tokenSource.IsCancellationRequested)
- {
- var receivedBytes = await socket.ReceiveAsync(receiveBuffer, 0, 64000);
+ // Enable listening for notifications (optional)
+ _DeviceLocator.StartListeningForNotifications();
- if (receivedBytes > 0)
- {
- var args = SsdpHelper.ParseSsdpResponse(receiveBuffer);
- args.EndPoint = endPoint;
- args.LocalEndPoint = new IPEndPoint(localIp, 0);
-
- _ssdpHandler.LogMessageReceived(args, true);
-
- TryCreateDevice(args);
- }
- }
+ await _DeviceLocator.SearchAsync().ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error searching for devices", ex);
}
- _logger.Info("SSDP listener - Task completed");
- }
- catch (OperationCanceledException)
- {
- }
- catch (Exception e)
- {
- _logger.ErrorException("Error in listener", e);
+ var delay = _config.GetDlnaConfiguration().ClientDiscoveryIntervalSeconds * 1000;
+
+ await Task.Delay(delay, _tokenSource.Token).ConfigureAwait(false);
}
- }, _tokenSource.Token, TaskCreationOptions.LongRunning);
+ }, CancellationToken.None, TaskCreationOptions.LongRunning);
}
- private void CreateNotifier(IPAddress localIp)
+ // Process each found device in the event handler
+ void deviceLocator_DeviceAvailable(object sender, DeviceAvailableEventArgs e)
{
- Task.Factory.StartNew(async (o) =>
- {
- try
- {
- while (true)
- {
- _ssdpHandler.SendSearchMessage(new IPEndPoint(localIp, 1900));
+ var originalHeaders = e.DiscoveredDevice.ResponseHeaders;
- var delay = _config.GetDlnaConfiguration().ClientDiscoveryIntervalSeconds * 1000;
+ var headerDict = originalHeaders == null ? new Dictionary<string, KeyValuePair<string, IEnumerable<string>>>() : originalHeaders.ToDictionary(i => i.Key, StringComparer.OrdinalIgnoreCase);
- await Task.Delay(delay, _tokenSource.Token).ConfigureAwait(false);
- }
- }
- catch (OperationCanceledException)
- {
- }
- catch (Exception ex)
+ var headers = headerDict.ToDictionary(i => i.Key, i => i.Value.Value.FirstOrDefault(), StringComparer.OrdinalIgnoreCase);
+
+ var args = new GenericEventArgs<UpnpDeviceInfo>
+ {
+ Argument = new UpnpDeviceInfo
{
- _logger.ErrorException("Error in notifier", ex);
+ Location = e.DiscoveredDevice.DescriptionLocation,
+ Headers = headers
}
+ };
- }, _tokenSource.Token, TaskCreationOptions.LongRunning);
+ EventHelper.FireEventIfNotNull(DeviceDiscovered, this, args, _logger);
}
- private Socket GetMulticastSocket(IPAddress localIpAddress, EndPoint localEndpoint)
+ private void _DeviceLocator_DeviceUnavailable(object sender, DeviceUnavailableEventArgs e)
{
- var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
- socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
- socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse("239.255.255.250"), localIpAddress));
- socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 4);
-
- socket.Bind(localEndpoint);
+ var originalHeaders = e.DiscoveredDevice.ResponseHeaders;
- return socket;
- }
+ var headerDict = originalHeaders == null ? new Dictionary<string, KeyValuePair<string, IEnumerable<string>>>() : originalHeaders.ToDictionary(i => i.Key, StringComparer.OrdinalIgnoreCase);
- private void TryCreateDevice(SsdpMessageEventArgs args)
- {
- string nts;
- args.Headers.TryGetValue("NTS", out nts);
+ var headers = headerDict.ToDictionary(i => i.Key, i => i.Value.Value.FirstOrDefault(), StringComparer.OrdinalIgnoreCase);
- if (String.Equals(nts, "ssdp:byebye", StringComparison.OrdinalIgnoreCase))
+ var args = new GenericEventArgs<UpnpDeviceInfo>
{
- if (String.Equals(args.Method, "NOTIFY", StringComparison.OrdinalIgnoreCase))
+ Argument = new UpnpDeviceInfo
{
- if (!_disposed)
- {
- EventHelper.FireEventIfNotNull(DeviceLeft, this, args, _logger);
- }
+ Location = e.DiscoveredDevice.DescriptionLocation,
+ Headers = headers
}
+ };
- return;
- }
-
- string usn;
- if (!args.Headers.TryGetValue("USN", out usn)) usn = string.Empty;
-
- string nt;
- if (!args.Headers.TryGetValue("NT", out nt)) nt = string.Empty;
-
- // Need to be able to download device description
- string location;
- if (!args.Headers.TryGetValue("Location", out location) ||
- string.IsNullOrEmpty(location))
- {
- return;
- }
+ EventHelper.FireEventIfNotNull(DeviceLeft, this, args, _logger);
+ }
- EventHelper.FireEventIfNotNull(DeviceDiscovered, this, args, _logger);
+ public void Start()
+ {
+ BeginSearch();
}
public void Dispose()
{
- if (_ssdpHandler != null)
- {
- _ssdpHandler.MessageReceived -= _ssdpHandler_MessageReceived;
- }
-
if (!_disposed)
{
_disposed = true;
diff --git a/MediaBrowser.Dlna/Ssdp/SsdpHandler.cs b/MediaBrowser.Dlna/Ssdp/SsdpHandler.cs
index 720ea71a0..0d0ca98a2 100644
--- a/MediaBrowser.Dlna/Ssdp/SsdpHandler.cs
+++ b/MediaBrowser.Dlna/Ssdp/SsdpHandler.cs
@@ -83,90 +83,6 @@ namespace MediaBrowser.Dlna.Ssdp
}
}
- public event EventHandler<SsdpMessageEventArgs> MessageReceived;
-
- private async void OnMessageReceived(SsdpMessageEventArgs args, bool isMulticast)
- {
- if (IgnoreMessage(args, isMulticast))
- {
- return;
- }
-
- LogMessageReceived(args, isMulticast);
-
- var headers = args.Headers;
- string st;
-
- if (string.Equals(args.Method, "M-SEARCH", StringComparison.OrdinalIgnoreCase) && headers.TryGetValue("st", out st))
- {
- TimeSpan delay = GetSearchDelay(headers);
-
- if (_config.GetDlnaConfiguration().EnableDebugLog)
- {
- _logger.Debug("Delaying search response by {0} seconds", delay.TotalSeconds);
- }
-
- await Task.Delay(delay).ConfigureAwait(false);
-
- RespondToSearch(args.EndPoint, st);
- }
-
- EventHelper.FireEventIfNotNull(MessageReceived, this, args, _logger);
- }
-
- internal void LogMessageReceived(SsdpMessageEventArgs args, bool isMulticast)
- {
- var enableDebugLogging = _config.GetDlnaConfiguration().EnableDebugLog;
-
- if (enableDebugLogging)
- {
- var headerTexts = args.Headers.Select(i => string.Format("{0}={1}", i.Key, i.Value));
- var headerText = string.Join(",", headerTexts.ToArray());
-
- var protocol = isMulticast ? "Multicast" : "Unicast";
- var localEndPointString = args.LocalEndPoint == null ? "null" : args.LocalEndPoint.ToString();
- _logger.Debug("{0} message received from {1} on {3}. Protocol: {4} Headers: {2}", args.Method, args.EndPoint, headerText, localEndPointString, protocol);
- }
- }
-
- internal bool IgnoreMessage(SsdpMessageEventArgs args, bool isMulticast)
- {
- string usn;
- if (args.Headers.TryGetValue("USN", out usn))
- {
- // USN=uuid:b67df29b5c379445fde78c3774ab518d::urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1
- if (RegisteredDevices.Any(i => string.Equals(i.USN, usn, StringComparison.OrdinalIgnoreCase)))
- {
- //var headerTexts = args.Headers.Select(i => string.Format("{0}={1}", i.Key, i.Value));
- //var headerText = string.Join(",", headerTexts.ToArray());
-
- //var protocol = isMulticast ? "Multicast" : "Unicast";
- //var localEndPointString = args.LocalEndPoint == null ? "null" : args.LocalEndPoint.ToString();
- //_logger.Debug("IGNORING {0} message received from {1} on {3}. Protocol: {4} Headers: {2}", args.Method, args.EndPoint, headerText, localEndPointString, protocol);
-
- return true;
- }
- }
-
- string serverId;
- if (args.Headers.TryGetValue("X-EMBY-SERVERID", out serverId))
- {
- if (string.Equals(serverId, _appHost.SystemId, StringComparison.OrdinalIgnoreCase))
- {
- //var headerTexts = args.Headers.Select(i => string.Format("{0}={1}", i.Key, i.Value));
- //var headerText = string.Join(",", headerTexts.ToArray());
-
- //var protocol = isMulticast ? "Multicast" : "Unicast";
- //var localEndPointString = args.LocalEndPoint == null ? "null" : args.LocalEndPoint.ToString();
- //_logger.Debug("IGNORING {0} message received from {1} on {3}. Protocol: {4} Headers: {2}", args.Method, args.EndPoint, headerText, localEndPointString, protocol);
-
- return true;
- }
- }
-
- return false;
- }
-
public IEnumerable<UpnpDevice> RegisteredDevices
{
get
@@ -188,8 +104,6 @@ namespace MediaBrowser.Dlna.Ssdp
RestartSocketListener();
ReloadAliveNotifier();
- CreateUnicastClient();
-
SystemEvents.PowerModeChanged -= SystemEvents_PowerModeChanged;
SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
}
@@ -202,32 +116,6 @@ namespace MediaBrowser.Dlna.Ssdp
}
}
- public void SendSearchMessage(EndPoint localIp)
- {
- var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
-
- values["HOST"] = "239.255.255.250:1900";
- values["USER-AGENT"] = "UPnP/1.0 DLNADOC/1.50 Platinum/1.0.4.2";
- values["X-EMBY-SERVERID"] = _appHost.SystemId;
-
- values["MAN"] = "\"ssdp:discover\"";
-
- // Search target
- values["ST"] = "ssdp:all";
-
- // Seconds to delay response
- values["MX"] = "3";
-
- var header = "M-SEARCH * HTTP/1.1";
-
- var msg = new SsdpMessageBuilder().BuildMessage(header, values);
-
- // UDP is unreliable, so send 3 requests at a time (per Upnp spec, sec 1.1.2)
- SendDatagram(msg, _ssdpEndp, localIp, true);
-
- SendUnicastRequest(msg);
- }
-
public async void SendDatagram(string msg,
EndPoint endpoint,
EndPoint localAddress,
@@ -248,75 +136,6 @@ namespace MediaBrowser.Dlna.Ssdp
}
}
- /// <summary>
- /// According to the spec: http://www.upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.0-20080424.pdf
- /// Device responses should be delayed a random duration between 0 and this many seconds to balance
- /// load for the control point when it processes responses. In my testing kodi times out after mx
- /// so we will generate from mx - 1
- /// </summary>
- /// <param name="headers">The mx headers</param>
- /// <returns>A timepsan for the amount to delay before returning search result.</returns>
- private TimeSpan GetSearchDelay(Dictionary<string, string> headers)
- {
- string mx;
- headers.TryGetValue("mx", out mx);
- int delaySeconds = 0;
- if (!string.IsNullOrWhiteSpace(mx)
- && int.TryParse(mx, NumberStyles.Any, CultureInfo.InvariantCulture, out delaySeconds)
- && delaySeconds > 1)
- {
- delaySeconds = new Random().Next(delaySeconds - 1);
- }
-
- return TimeSpan.FromSeconds(delaySeconds);
- }
-
- private void RespondToSearch(EndPoint endpoint, string deviceType)
- {
- var enableDebugLogging = _config.GetDlnaConfiguration().EnableDebugLog;
-
- var isLogged = false;
-
- const string header = "HTTP/1.1 200 OK";
-
- foreach (var d in RegisteredDevices)
- {
- if (string.Equals(deviceType, "ssdp:all", StringComparison.OrdinalIgnoreCase) ||
- string.Equals(deviceType, d.Type, StringComparison.OrdinalIgnoreCase))
- {
- if (!isLogged)
- {
- if (enableDebugLogging)
- {
- _logger.Debug("Responding to search from {0} for {1}", endpoint, deviceType);
- }
- isLogged = true;
- }
-
- var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
-
- values["CACHE-CONTROL"] = "max-age = 600";
- values["DATE"] = DateTime.Now.ToString("R");
- values["EXT"] = "";
- values["LOCATION"] = d.Descriptor.ToString();
- values["SERVER"] = _serverSignature;
- values["ST"] = d.Type;
- values["USN"] = d.USN;
-
- var msg = new SsdpMessageBuilder().BuildMessage(header, values);
-
- SendDatagram(msg, endpoint, null, false, 2);
- SendDatagram(msg, endpoint, new IPEndPoint(d.Address, 0), false, 2);
- //SendDatagram(header, values, endpoint, null, true);
-
- if (enableDebugLogging)
- {
- _logger.Debug("{1} - Responded to a {0} request to {2}", d.Type, endpoint, d.Address.ToString());
- }
- }
- }
- }
-
private void RestartSocketListener()
{
if (_isDisposed)
@@ -329,8 +148,6 @@ namespace MediaBrowser.Dlna.Ssdp
_multicastSocket = CreateMulticastSocket();
_logger.Info("MultiCast socket created");
-
- Receive();
}
catch (Exception ex)
{
@@ -339,74 +156,6 @@ namespace MediaBrowser.Dlna.Ssdp
}
}
- private void Receive()
- {
- try
- {
- var buffer = new byte[1024];
-
- EndPoint endpoint = new IPEndPoint(IPAddress.Any, SSDPPort);
-
- _multicastSocket.BeginReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref endpoint, ReceiveCallback, buffer);
- }
- catch (ObjectDisposedException)
- {
- if (!_isDisposed)
- {
- //StartSocketRetryTimer();
- }
- }
- catch (Exception ex)
- {
- _logger.Debug("Error in BeginReceiveFrom", ex);
- }
- }
-
- private void ReceiveCallback(IAsyncResult result)
- {
- if (_isDisposed)
- {
- return;
- }
-
- try
- {
- EndPoint endpoint = new IPEndPoint(IPAddress.Any, SSDPPort);
-
- var length = _multicastSocket.EndReceiveFrom(result, ref endpoint);
-
- var received = (byte[])result.AsyncState;
-
- var enableDebugLogging = _config.GetDlnaConfiguration().EnableDebugLog;
-
- if (enableDebugLogging)
- {
- _logger.Debug(Encoding.ASCII.GetString(received));
- }
-
- var args = SsdpHelper.ParseSsdpResponse(received);
- args.EndPoint = endpoint;
-
- OnMessageReceived(args, true);
- }
- catch (ObjectDisposedException)
- {
- if (!_isDisposed)
- {
- //StartSocketRetryTimer();
- }
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Failed to read SSDP message", ex);
- }
-
- if (_multicastSocket != null)
- {
- Receive();
- }
- }
-
public void Dispose()
{
_config.NamedConfigurationUpdated -= _config_ConfigurationUpdated;
@@ -414,7 +163,6 @@ namespace MediaBrowser.Dlna.Ssdp
_isDisposed = true;
- DisposeUnicastClient();
DisposeSocket();
StopAliveNotifier();
}
@@ -523,137 +271,6 @@ namespace MediaBrowser.Dlna.Ssdp
}
}
- private void CreateUnicastClient()
- {
- if (_unicastClient == null)
- {
- try
- {
- _unicastClient = new UdpClient(_unicastPort);
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error creating unicast client", ex);
- }
-
- UnicastSetBeginReceive();
- }
- }
-
- private void DisposeUnicastClient()
- {
- if (_unicastClient != null)
- {
- try
- {
- _unicastClient.Close();
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error closing unicast client", ex);
- }
-
- _unicastClient = null;
- }
- }
-
- /// <summary>
- /// Listen for Unicast SSDP Responses
- /// </summary>
- private void UnicastSetBeginReceive()
- {
- try
- {
- var ipRxEnd = new IPEndPoint(IPAddress.Any, _unicastPort);
- var udpListener = new UdpState { EndPoint = ipRxEnd };
-
- udpListener.UdpClient = _unicastClient;
- _unicastClient.BeginReceive(UnicastReceiveCallback, udpListener);
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error in UnicastSetBeginReceive", ex);
- }
- }
-
- /// <summary>
- /// The UnicastReceiveCallback receives Http Responses
- /// and Fired the SatIpDeviceFound Event for adding the SatIpDevice
- /// </summary>
- /// <param name="ar"></param>
- private void UnicastReceiveCallback(IAsyncResult ar)
- {
- var udpClient = ((UdpState)(ar.AsyncState)).UdpClient;
- var endpoint = ((UdpState)(ar.AsyncState)).EndPoint;
- if (udpClient.Client != null)
- {
- try
- {
- var responseBytes = udpClient.EndReceive(ar, ref endpoint);
- var args = SsdpHelper.ParseSsdpResponse(responseBytes);
-
- args.EndPoint = endpoint;
-
- OnMessageReceived(args, false);
-
- UnicastSetBeginReceive();
- }
- catch (ObjectDisposedException)
- {
-
- }
- catch (SocketException)
- {
-
- }
- catch (Exception)
- {
- // If called while shutting down, seeing a NullReferenceException inside EndReceive
- }
- }
- }
-
- private void SendUnicastRequest(string request, int sendCount = 3)
- {
- if (_unicastClient == null)
- {
- return;
- }
-
- var ipSsdp = IPAddress.Parse(SSDPAddr);
- var ipTxEnd = new IPEndPoint(ipSsdp, SSDPPort);
-
- SendUnicastRequest(request, ipTxEnd, sendCount);
- }
-
- private async void SendUnicastRequest(string request, IPEndPoint toEndPoint, int sendCount = 3)
- {
- if (_unicastClient == null)
- {
- return;
- }
-
- //_logger.Debug("Sending unicast request");
-
- byte[] req = Encoding.ASCII.GetBytes(request);
-
- try
- {
- for (var i = 0; i < sendCount; i++)
- {
- if (i > 0)
- {
- await Task.Delay(50).ConfigureAwait(false);
- }
- _unicastClient.Send(req, req.Length, toEndPoint);
- }
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error in SendUnicastRequest", ex);
- }
- }
-
private readonly object _notificationTimerSyncLock = new object();
private int _aliveNotifierIntervalMs;
private void ReloadAliveNotifier()
diff --git a/MediaBrowser.Dlna/Ssdp/SsdpHelper.cs b/MediaBrowser.Dlna/Ssdp/SsdpHelper.cs
deleted file mode 100644
index d196e9851..000000000
--- a/MediaBrowser.Dlna/Ssdp/SsdpHelper.cs
+++ /dev/null
@@ -1,45 +0,0 @@
-using MediaBrowser.Controller.Dlna;
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Text;
-
-namespace MediaBrowser.Dlna.Ssdp
-{
- public class SsdpHelper
- {
- public static SsdpMessageEventArgs ParseSsdpResponse(byte[] data)
- {
- using (var ms = new MemoryStream(data))
- {
- using (var reader = new StreamReader(ms, Encoding.ASCII))
- {
- var proto = (reader.ReadLine() ?? string.Empty).Trim();
- var method = proto.Split(new[] { ' ' }, 2)[0];
- var headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
- for (var line = reader.ReadLine(); line != null; line = reader.ReadLine())
- {
- line = line.Trim();
- if (string.IsNullOrEmpty(line))
- {
- break;
- }
- var parts = line.Split(new[] { ':' }, 2);
-
- if (parts.Length >= 2)
- {
- headers[parts[0]] = parts[1].Trim();
- }
- }
-
- return new SsdpMessageEventArgs
- {
- Method = method,
- Headers = headers,
- Message = data
- };
- }
- }
- }
- }
-}
diff --git a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs
index fe61a7a46..ef9160b70 100644
--- a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs
+++ b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs
@@ -132,7 +132,7 @@ namespace MediaBrowser.LocalMetadata.Images
}
var imagePrefix = item.FileNameWithoutExtension + "-";
- var isInMixedFolder = item.IsInMixedFolder;
+ var isInMixedFolder = item.DetectIsInMixedFolder();
PopulatePrimaryImages(item, images, files, imagePrefix, isInMixedFolder);
diff --git a/MediaBrowser.LocalMetadata/Savers/GameXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/GameXmlSaver.cs
index a90789a3e..5592c068c 100644
--- a/MediaBrowser.LocalMetadata/Savers/GameXmlSaver.cs
+++ b/MediaBrowser.LocalMetadata/Savers/GameXmlSaver.cs
@@ -99,7 +99,7 @@ namespace MediaBrowser.LocalMetadata.Savers
public static string GetGameSavePath(Game item)
{
- if (item.IsInMixedFolder)
+ if (item.DetectIsInMixedFolder())
{
return Path.ChangeExtension(item.Path, ".xml");
}
diff --git a/MediaBrowser.LocalMetadata/Savers/PersonXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/PersonXmlSaver.cs
index bf6ed03ad..295e64881 100644
--- a/MediaBrowser.LocalMetadata/Savers/PersonXmlSaver.cs
+++ b/MediaBrowser.LocalMetadata/Savers/PersonXmlSaver.cs
@@ -66,9 +66,9 @@ namespace MediaBrowser.LocalMetadata.Savers
XmlSaverHelpers.AddCommonNodes(person, _libraryManager, builder);
- if (!string.IsNullOrEmpty(person.PlaceOfBirth))
+ if (person.ProductionLocations.Count > 0)
{
- builder.Append("<PlaceOfBirth>" + SecurityElement.Escape(person.PlaceOfBirth) + "</PlaceOfBirth>");
+ builder.Append("<PlaceOfBirth>" + SecurityElement.Escape(person.ProductionLocations[0]) + "</PlaceOfBirth>");
}
builder.Append("</Item>");
diff --git a/MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs b/MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs
index ac4ebbefe..c9810b042 100644
--- a/MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs
+++ b/MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs
@@ -350,21 +350,17 @@ namespace MediaBrowser.LocalMetadata.Savers
}
}
- var hasProductionLocations = item as IHasProductionLocations;
- if (hasProductionLocations != null)
- {
- if (hasProductionLocations.ProductionLocations.Count > 0)
- {
- builder.Append("<Countries>");
+ //if (hasProductionLocations.ProductionLocations.Count > 0)
+ //{
+ // builder.Append("<Countries>");
- foreach (var name in hasProductionLocations.ProductionLocations)
- {
- builder.Append("<Country>" + SecurityElement.Escape(name) + "</Country>");
- }
+ // foreach (var name in hasProductionLocations.ProductionLocations)
+ // {
+ // builder.Append("<Country>" + SecurityElement.Escape(name) + "</Country>");
+ // }
- builder.Append("</Countries>");
- }
- }
+ // builder.Append("</Countries>");
+ //}
var hasDisplayOrder = item as IHasDisplayOrder;
if (hasDisplayOrder != null && !string.IsNullOrEmpty(hasDisplayOrder.DisplayOrder))
@@ -457,20 +453,11 @@ namespace MediaBrowser.LocalMetadata.Savers
}
}
- var hasTagline = item as IHasTaglines;
- if (hasTagline != null)
+ if (!string.IsNullOrWhiteSpace(item.Tagline))
{
- if (hasTagline.Taglines.Count > 0)
- {
- builder.Append("<Taglines>");
-
- foreach (var tagline in hasTagline.Taglines)
- {
- builder.Append("<Tagline>" + SecurityElement.Escape(tagline) + "</Tagline>");
- }
-
- builder.Append("</Taglines>");
- }
+ builder.Append("<Taglines>");
+ builder.Append("<Tagline>" + SecurityElement.Escape(item.Tagline) + "</Tagline>");
+ builder.Append("</Taglines>");
}
if (item.Genres.Count > 0)
diff --git a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs
index 2ded8a66f..c7b78aae3 100644
--- a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs
@@ -431,10 +431,10 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
if (state.PlayableStreamFileNames.Count > 0)
{
- return MediaEncoder.GetProbeSizeArgument(state.PlayableStreamFileNames.ToArray(), state.InputProtocol);
+ return MediaEncoder.GetProbeSizeAndAnalyzeDurationArgument(state.PlayableStreamFileNames.ToArray(), state.InputProtocol);
}
- return MediaEncoder.GetProbeSizeArgument(new[] { state.MediaPath }, state.InputProtocol);
+ return MediaEncoder.GetProbeSizeAndAnalyzeDurationArgument(new[] { state.MediaPath }, state.InputProtocol);
}
/// <summary>
@@ -487,7 +487,15 @@ namespace MediaBrowser.MediaEncoding.Encoder
var videoEncoder = EncodingJobFactory.GetVideoEncoder(MediaEncoder, state, encodingOptions);
if (videoEncoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1)
{
- arg = "-hwaccel vaapi -hwaccel_output_format vaapi -vaapi_device " + encodingOptions.VaapiDevice + " " + arg;
+ var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.Options.SubtitleMethod == SubtitleDeliveryMethod.Encode;
+ var hwOutputFormat = "vaapi";
+
+ if (hasGraphicalSubs)
+ {
+ hwOutputFormat = "yuv420p";
+ }
+
+ arg = "-hwaccel vaapi -hwaccel_output_format " + hwOutputFormat + " -vaapi_device " + encodingOptions.VaapiDevice + " " + arg;
}
}
@@ -565,7 +573,20 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
outputSizeParam = await GetOutputSizeParam(state, outputVideoCodec).ConfigureAwait(false);
outputSizeParam = outputSizeParam.TrimEnd('"');
- outputSizeParam = "," + outputSizeParam.Substring(outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase));
+
+ if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
+ {
+ outputSizeParam = "," + outputSizeParam.Substring(outputSizeParam.IndexOf("format", StringComparison.OrdinalIgnoreCase));
+ }
+ else
+ {
+ outputSizeParam = "," + outputSizeParam.Substring(outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase));
+ }
+ }
+
+ if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase) && outputSizeParam.Length == 0)
+ {
+ outputSizeParam = ",format=nv12|vaapi,hwupload";
}
var videoSizeParam = string.Empty;
@@ -705,13 +726,15 @@ namespace MediaBrowser.MediaEncoding.Encoder
levelString = NormalizeTranscodingLevel(state.OutputVideoCodec, levelString);
// h264_qsv and h264_nvenc expect levels to be expressed as a decimal. libx264 supports decimal and non-decimal format
+ // also needed for libx264 due to https://trac.ffmpeg.org/ticket/3307
if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) ||
- string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase))
+ string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase))
{
switch (levelString)
{
case "30":
- param += " -level 3";
+ param += " -level 3.0";
break;
case "31":
param += " -level 3.1";
@@ -720,7 +743,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
param += " -level 3.2";
break;
case "40":
- param += " -level 4";
+ param += " -level 4.0";
break;
case "41":
param += " -level 4.1";
@@ -729,7 +752,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
param += " -level 4.2";
break;
case "50":
- param += " -level 5";
+ param += " -level 5.0";
break;
case "51":
param += " -level 5.1";
diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
index a450097fd..24de4e77e 100644
--- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
@@ -26,6 +26,30 @@ namespace MediaBrowser.MediaEncoding.Encoder
return new Tuple<List<string>, List<string>>(decoders, encoders);
}
+ public bool ValidateVersion(string encoderAppPath)
+ {
+ string output = string.Empty;
+ try
+ {
+ output = GetProcessOutput(encoderAppPath, "-version");
+ }
+ catch
+ {
+ }
+
+ if (string.IsNullOrWhiteSpace(output))
+ {
+ return false;
+ }
+
+ if (output.IndexOf("Libav developers", StringComparison.OrdinalIgnoreCase) != -1)
+ {
+ return false;
+ }
+
+ return true;
+ }
+
private List<string> GetDecoders(string encoderAppPath)
{
string output = string.Empty;
diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs
index 490a51128..0c7ff1b76 100644
--- a/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs
@@ -140,7 +140,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
try
{
- await _mediaSourceManager.CloseLiveStream(MediaSource.LiveStreamId, CancellationToken.None).ConfigureAwait(false);
+ await _mediaSourceManager.CloseLiveStream(MediaSource.LiveStreamId).ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -392,6 +392,19 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
}
+ public bool? IsTargetAVC
+ {
+ get
+ {
+ if (Options.Static)
+ {
+ return VideoStream == null ? null : VideoStream.IsAVC;
+ }
+
+ return false;
+ }
+ }
+
public int? TargetVideoStreamCount
{
get
diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs
index c72532669..b66a3ed96 100644
--- a/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs
@@ -823,7 +823,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
state.TargetRefFrames,
state.TargetVideoStreamCount,
state.TargetAudioStreamCount,
- state.TargetVideoCodecTag);
+ state.TargetVideoCodecTag,
+ state.IsTargetAVC);
if (mediaProfile != null)
{
diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs
index b34a4cd38..cec272b39 100644
--- a/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs
@@ -26,6 +26,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
return string.Format("\"{0}\"", url);
}
+ if (protocol == MediaProtocol.Udp)
+ {
+ var url = inputFiles.First();
+
+ return string.Format("\"{0}\"", url);
+ }
return GetConcatInputArgument(inputFiles);
}
@@ -74,9 +80,14 @@ namespace MediaBrowser.MediaEncoding.Encoder
return path.Replace("\"", "\\\"");
}
- public static string GetProbeSizeArgument(bool isDvd)
+ public static string GetProbeSizeArgument(int numInputFiles)
+ {
+ return numInputFiles > 1 ? "-probesize 1G" : "";
+ }
+
+ public static string GetAnalyzeDurationArgument(int numInputFiles)
{
- return isDvd ? "-probesize 1G -analyzeduration 200M" : string.Empty;
+ return numInputFiles > 1 ? "-analyzeduration 200M" : "";
}
}
}
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
index a2707aace..fc1444e1b 100644
--- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
@@ -24,6 +24,7 @@ using CommonIO;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net;
namespace MediaBrowser.MediaEncoding.Encoder
@@ -79,11 +80,14 @@ namespace MediaBrowser.MediaEncoding.Encoder
protected readonly Func<IMediaSourceManager> MediaSourceManager;
private readonly IHttpClient _httpClient;
private readonly IZipClient _zipClient;
+ private readonly IMemoryStreamProvider _memoryStreamProvider;
private readonly List<ProcessWrapper> _runningProcesses = new List<ProcessWrapper>();
private readonly bool _hasExternalEncoder;
+ private string _originalFFMpegPath;
+ private string _originalFFProbePath;
- public MediaEncoder(ILogger logger, IJsonSerializer jsonSerializer, string ffMpegPath, string ffProbePath, bool hasExternalEncoder, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ILiveTvManager liveTvManager, IIsoManager isoManager, ILibraryManager libraryManager, IChannelManager channelManager, ISessionManager sessionManager, Func<ISubtitleEncoder> subtitleEncoder, Func<IMediaSourceManager> mediaSourceManager, IHttpClient httpClient, IZipClient zipClient)
+ public MediaEncoder(ILogger logger, IJsonSerializer jsonSerializer, string ffMpegPath, string ffProbePath, bool hasExternalEncoder, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ILiveTvManager liveTvManager, IIsoManager isoManager, ILibraryManager libraryManager, IChannelManager channelManager, ISessionManager sessionManager, Func<ISubtitleEncoder> subtitleEncoder, Func<IMediaSourceManager> mediaSourceManager, IHttpClient httpClient, IZipClient zipClient, IMemoryStreamProvider memoryStreamProvider)
{
_logger = logger;
_jsonSerializer = jsonSerializer;
@@ -98,8 +102,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
MediaSourceManager = mediaSourceManager;
_httpClient = httpClient;
_zipClient = zipClient;
+ _memoryStreamProvider = memoryStreamProvider;
FFProbePath = ffProbePath;
FFMpegPath = ffMpegPath;
+ _originalFFProbePath = ffProbePath;
+ _originalFFMpegPath = ffMpegPath;
_hasExternalEncoder = hasExternalEncoder;
}
@@ -108,11 +115,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
get
{
- if (_hasExternalEncoder)
- {
- return "External";
- }
-
if (string.IsNullOrWhiteSpace(FFMpegPath))
{
return null;
@@ -177,12 +179,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
ConfigureEncoderPaths();
- if (_hasExternalEncoder)
- {
- LogPaths();
- return;
- }
-
// If the path was passed in, save it into config now.
var encodingOptions = GetEncodingOptions();
var appPath = encodingOptions.EncoderAppPath;
@@ -207,11 +203,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
public async Task UpdateEncoderPath(string path, string pathType)
{
- if (_hasExternalEncoder)
- {
- return;
- }
-
Tuple<string, string> newPaths;
if (string.Equals(pathType, "system", StringComparison.OrdinalIgnoreCase))
@@ -247,6 +238,13 @@ namespace MediaBrowser.MediaEncoding.Encoder
throw new ResourceNotFoundException("ffprobe not found");
}
+ path = newPaths.Item1;
+
+ if (!ValidateVersion(path))
+ {
+ throw new ResourceNotFoundException("ffmpeg version 3.0 or greater is required.");
+ }
+
var config = GetEncodingOptions();
config.EncoderAppPath = path;
ConfigurationManager.SaveConfiguration("encoding", config);
@@ -254,13 +252,13 @@ namespace MediaBrowser.MediaEncoding.Encoder
Init();
}
- private void ConfigureEncoderPaths()
+ private bool ValidateVersion(string path)
{
- if (_hasExternalEncoder)
- {
- return;
- }
+ return new EncoderValidator(_logger).ValidateVersion(path);
+ }
+ private void ConfigureEncoderPaths()
+ {
var appPath = GetEncodingOptions().EncoderAppPath;
if (string.IsNullOrWhiteSpace(appPath))
@@ -308,45 +306,22 @@ namespace MediaBrowser.MediaEncoding.Encoder
string encoderPath = null;
string probePath = null;
- if (TestSystemInstalled("ffmpeg"))
- {
- encoderPath = "ffmpeg";
- }
- if (TestSystemInstalled("ffprobe"))
+ if (_hasExternalEncoder && ValidateVersion(_originalFFMpegPath))
{
- probePath = "ffprobe";
+ encoderPath = _originalFFMpegPath;
+ probePath = _originalFFProbePath;
}
- return new Tuple<string, string>(encoderPath, probePath);
- }
-
- private bool TestSystemInstalled(string app)
- {
- try
+ if (string.IsNullOrWhiteSpace(encoderPath))
{
- var startInfo = new ProcessStartInfo
+ if (ValidateVersion("ffmpeg") && ValidateVersion("ffprobe"))
{
- FileName = app,
- Arguments = "-v",
- UseShellExecute = false,
- CreateNoWindow = true,
- WindowStyle = ProcessWindowStyle.Hidden,
- ErrorDialog = false
- };
-
- using (var process = Process.Start(startInfo))
- {
- process.WaitForExit();
+ encoderPath = "ffmpeg";
+ probePath = "ffprobe";
}
-
- _logger.Debug("System app installed: " + app);
- return true;
- }
- catch
- {
- _logger.Debug("System app not installed: " + app);
- return false;
}
+
+ return new Tuple<string, string>(encoderPath, probePath);
}
private Tuple<string, string> GetPathsFromDirectory(string path)
@@ -451,8 +426,24 @@ namespace MediaBrowser.MediaEncoding.Encoder
var inputFiles = MediaEncoderHelpers.GetInputArgument(FileSystem, request.InputPath, request.Protocol, request.MountedIso, request.PlayableStreamFileNames);
+ var probeSize = EncodingUtils.GetProbeSizeArgument(inputFiles.Length);
+ string analyzeDuration;
+
+ if (request.AnalyzeDurationSections > 0)
+ {
+ analyzeDuration = "-analyzeduration " +
+ (request.AnalyzeDurationSections*1000000).ToString(CultureInfo.InvariantCulture);
+ }
+ else
+ {
+ analyzeDuration = EncodingUtils.GetAnalyzeDurationArgument(inputFiles.Length);
+ }
+
+ probeSize = probeSize + " " + analyzeDuration;
+ probeSize = probeSize.Trim();
+
return GetMediaInfoInternal(GetInputArgument(inputFiles, request.Protocol), request.InputPath, request.Protocol, extractChapters,
- GetProbeSizeArgument(inputFiles, request.Protocol), request.MediaType == DlnaProfileType.Audio, request.VideoType, cancellationToken);
+ probeSize, request.MediaType == DlnaProfileType.Audio, request.VideoType, cancellationToken);
}
/// <summary>
@@ -473,9 +464,23 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// <param name="inputFiles">The input files.</param>
/// <param name="protocol">The protocol.</param>
/// <returns>System.String.</returns>
- public string GetProbeSizeArgument(string[] inputFiles, MediaProtocol protocol)
+ public string GetProbeSizeAndAnalyzeDurationArgument(string[] inputFiles, MediaProtocol protocol)
{
- return EncodingUtils.GetProbeSizeArgument(inputFiles.Length > 1);
+ var results = new List<string>();
+
+ var probeSize = EncodingUtils.GetProbeSizeArgument(inputFiles.Length);
+ var analyzeDuration = EncodingUtils.GetAnalyzeDurationArgument(inputFiles.Length);
+
+ if (!string.IsNullOrWhiteSpace(probeSize))
+ {
+ results.Add(probeSize);
+ }
+
+ if (!string.IsNullOrWhiteSpace(analyzeDuration))
+ {
+ results.Add(analyzeDuration);
+ }
+ return string.Join(" ", results.ToArray());
}
/// <summary>
@@ -572,7 +577,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
}
- var mediaInfo = new ProbeResultNormalizer(_logger, FileSystem).GetMediaInfo(result, videoType, isAudio, primaryPath, protocol);
+ var mediaInfo = new ProbeResultNormalizer(_logger, FileSystem, _memoryStreamProvider).GetMediaInfo(result, videoType, isAudio, primaryPath, protocol);
var videoStream = mediaInfo.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);
@@ -799,20 +804,20 @@ namespace MediaBrowser.MediaEncoding.Encoder
public Task<string> ExtractAudioImage(string path, int? imageStreamIndex, CancellationToken cancellationToken)
{
- return ExtractImage(new[] { path }, imageStreamIndex, MediaProtocol.File, true, null, null, cancellationToken);
+ return ExtractImage(new[] { path }, null, imageStreamIndex, MediaProtocol.File, true, null, null, cancellationToken);
}
- public Task<string> ExtractVideoImage(string[] inputFiles, MediaProtocol protocol, Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken)
+ public Task<string> ExtractVideoImage(string[] inputFiles, string container, MediaProtocol protocol, Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken)
{
- return ExtractImage(inputFiles, null, protocol, false, threedFormat, offset, cancellationToken);
+ return ExtractImage(inputFiles, container, null, protocol, false, threedFormat, offset, cancellationToken);
}
- public Task<string> ExtractVideoImage(string[] inputFiles, MediaProtocol protocol, int? imageStreamIndex, CancellationToken cancellationToken)
+ public Task<string> ExtractVideoImage(string[] inputFiles, string container, MediaProtocol protocol, int? imageStreamIndex, CancellationToken cancellationToken)
{
- return ExtractImage(inputFiles, imageStreamIndex, protocol, false, null, null, cancellationToken);
+ return ExtractImage(inputFiles, container, imageStreamIndex, protocol, false, null, null, cancellationToken);
}
- private async Task<string> ExtractImage(string[] inputFiles, int? imageStreamIndex, MediaProtocol protocol, bool isAudio,
+ private async Task<string> ExtractImage(string[] inputFiles, string container, int? imageStreamIndex, MediaProtocol protocol, bool isAudio,
Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken)
{
var resourcePool = isAudio ? _audioImageResourcePool : _videoImageResourcePool;
@@ -831,7 +836,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
try
{
- return await ExtractImageInternal(inputArgument, imageStreamIndex, protocol, threedFormat, offset, true, resourcePool, cancellationToken).ConfigureAwait(false);
+ return await ExtractImageInternal(inputArgument, container, imageStreamIndex, protocol, threedFormat, offset, true, resourcePool, cancellationToken).ConfigureAwait(false);
}
catch (ArgumentException)
{
@@ -843,10 +848,10 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
}
- return await ExtractImageInternal(inputArgument, imageStreamIndex, protocol, threedFormat, offset, false, resourcePool, cancellationToken).ConfigureAwait(false);
+ return await ExtractImageInternal(inputArgument, container, imageStreamIndex, protocol, threedFormat, offset, false, resourcePool, cancellationToken).ConfigureAwait(false);
}
- private async Task<string> ExtractImageInternal(string inputPath, int? imageStreamIndex, MediaProtocol protocol, Video3DFormat? threedFormat, TimeSpan? offset, bool useIFrame, SemaphoreSlim resourcePool, CancellationToken cancellationToken)
+ private async Task<string> ExtractImageInternal(string inputPath, string container, int? imageStreamIndex, MediaProtocol protocol, Video3DFormat? threedFormat, TimeSpan? offset, bool useIFrame, SemaphoreSlim resourcePool, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(inputPath))
{
@@ -887,11 +892,14 @@ namespace MediaBrowser.MediaEncoding.Encoder
var mapArg = imageStreamIndex.HasValue ? (" -map 0:v:" + imageStreamIndex.Value.ToString(CultureInfo.InvariantCulture)) : string.Empty;
+ var enableThumbnail = !new List<string> { "wtv" }.Contains(container ?? string.Empty, StringComparer.OrdinalIgnoreCase);
+ var thumbnail = enableThumbnail ? ",thumbnail=30" : string.Empty;
+
// Use ffmpeg to sample 100 (we can drop this if required using thumbnail=50 for 50 frames) frames and pick the best thumbnail. Have a fall back just in case.
- var args = useIFrame ? string.Format("-i {0}{3} -threads 0 -v quiet -vframes 1 -vf \"{2},thumbnail=30\" -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg) :
+ var args = useIFrame ? string.Format("-i {0}{3} -threads 0 -v quiet -vframes 1 -vf \"{2}{4}\" -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg, thumbnail) :
string.Format("-i {0}{3} -threads 0 -v quiet -vframes 1 -vf \"{2}\" -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg);
- var probeSize = GetProbeSizeArgument(new[] { inputPath }, protocol);
+ var probeSize = GetProbeSizeAndAnalyzeDurationArgument(new[] { inputPath }, protocol);
if (!string.IsNullOrEmpty(probeSize))
{
@@ -1002,7 +1010,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
var args = string.Format("-i {0} -threads 0 -v quiet -vf \"{2}\" -f image2 \"{1}\"", inputArgument, outputPath, vf);
- var probeSize = GetProbeSizeArgument(new[] { inputArgument }, protocol);
+ var probeSize = GetProbeSizeAndAnalyzeDurationArgument(new[] { inputArgument }, protocol);
if (!string.IsNullOrEmpty(probeSize))
{
diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
index 5805127e6..9f51dcf66 100644
--- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
+++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
@@ -9,6 +9,8 @@ using System.Linq;
using System.Text;
using System.Xml;
using CommonIO;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.MediaInfo;
@@ -20,11 +22,13 @@ namespace MediaBrowser.MediaEncoding.Probing
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private readonly ILogger _logger;
private readonly IFileSystem _fileSystem;
+ private readonly IMemoryStreamProvider _memoryStreamProvider;
- public ProbeResultNormalizer(ILogger logger, IFileSystem fileSystem)
+ public ProbeResultNormalizer(ILogger logger, IFileSystem fileSystem, IMemoryStreamProvider memoryStreamProvider)
{
_logger = logger;
_fileSystem = fileSystem;
+ _memoryStreamProvider = memoryStreamProvider;
}
public MediaInfo GetMediaInfo(InternalMediaInfoResult data, VideoType videoType, bool isAudio, string path, MediaProtocol protocol)
@@ -187,7 +191,7 @@ namespace MediaBrowser.MediaEncoding.Probing
xml = "<?xml version=\"1.0\"?>" + xml;
// <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>cast</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>name</key>\n\t\t\t<string>Blender Foundation</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>name</key>\n\t\t\t<string>Janus Bager Kristensen</string>\n\t\t</dict>\n\t</array>\n\t<key>directors</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>name</key>\n\t\t\t<string>Sacha Goedegebure</string>\n\t\t</dict>\n\t</array>\n\t<key>studio</key>\n\t<string>Blender Foundation</string>\n</dict>\n</plist>\n
- using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(xml)))
+ using (var stream = _memoryStreamProvider.CreateNew(Encoding.UTF8.GetBytes(xml)))
{
using (var streamReader = new StreamReader(stream))
{
@@ -573,8 +577,7 @@ namespace MediaBrowser.MediaEncoding.Probing
private void NormalizeStreamTitle(MediaStream stream)
{
- if (string.Equals(stream.Title, "sdh", StringComparison.OrdinalIgnoreCase) ||
- string.Equals(stream.Title, "cc", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(stream.Title, "cc", StringComparison.OrdinalIgnoreCase))
{
stream.Title = null;
}
diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
index dc5e69283..6dda49a55 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
@@ -18,6 +18,8 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.IO;
using UniversalDetector;
namespace MediaBrowser.MediaEncoding.Subtitles
@@ -32,8 +34,9 @@ namespace MediaBrowser.MediaEncoding.Subtitles
private readonly IJsonSerializer _json;
private readonly IHttpClient _httpClient;
private readonly IMediaSourceManager _mediaSourceManager;
+ private readonly IMemoryStreamProvider _memoryStreamProvider;
- public SubtitleEncoder(ILibraryManager libraryManager, ILogger logger, IApplicationPaths appPaths, IFileSystem fileSystem, IMediaEncoder mediaEncoder, IJsonSerializer json, IHttpClient httpClient, IMediaSourceManager mediaSourceManager)
+ public SubtitleEncoder(ILibraryManager libraryManager, ILogger logger, IApplicationPaths appPaths, IFileSystem fileSystem, IMediaEncoder mediaEncoder, IJsonSerializer json, IHttpClient httpClient, IMediaSourceManager mediaSourceManager, IMemoryStreamProvider memoryStreamProvider)
{
_libraryManager = libraryManager;
_logger = logger;
@@ -43,6 +46,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
_json = json;
_httpClient = httpClient;
_mediaSourceManager = mediaSourceManager;
+ _memoryStreamProvider = memoryStreamProvider;
}
private string SubtitleCachePath
@@ -61,7 +65,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
bool preserveOriginalTimestamps,
CancellationToken cancellationToken)
{
- var ms = new MemoryStream();
+ var ms = _memoryStreamProvider.CreateNew();
try
{
@@ -202,7 +206,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
var bytes = Encoding.UTF8.GetBytes(text);
- return new MemoryStream(bytes);
+ return _memoryStreamProvider.CreateNew(bytes);
}
}
}
diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
index 351740e6e..fa1acdca9 100644
--- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
+++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
@@ -160,24 +160,12 @@
<Compile Include="..\MediaBrowser.Model\Channels\ChannelQuery.cs">
<Link>Channels\ChannelQuery.cs</Link>
</Compile>
- <Compile Include="..\MediaBrowser.Model\Chapters\ChapterProviderInfo.cs">
- <Link>Chapters\ChapterProviderInfo.cs</Link>
- </Compile>
- <Compile Include="..\MediaBrowser.Model\Chapters\RemoteChapterInfo.cs">
- <Link>Chapters\RemoteChapterInfo.cs</Link>
- </Compile>
- <Compile Include="..\MediaBrowser.Model\Chapters\RemoteChapterResult.cs">
- <Link>Chapters\RemoteChapterResult.cs</Link>
- </Compile>
<Compile Include="..\MediaBrowser.Model\Collections\CollectionCreationResult.cs">
<Link>Collections\CollectionCreationResult.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\Configuration\AccessSchedule.cs">
<Link>Configuration\AccessSchedule.cs</Link>
</Compile>
- <Compile Include="..\MediaBrowser.Model\Configuration\AutoOnOff.cs">
- <Link>Configuration\AutoOnOff.cs</Link>
- </Compile>
<Compile Include="..\MediaBrowser.Model\Configuration\BaseApplicationConfiguration.cs">
<Link>Configuration\BaseApplicationConfiguration.cs</Link>
</Compile>
@@ -1177,9 +1165,6 @@
<Compile Include="..\MediaBrowser.Model\Updates\PackageTargetSystem.cs">
<Link>Updates\PackageTargetSystem.cs</Link>
</Compile>
- <Compile Include="..\MediaBrowser.Model\Updates\PackageType.cs">
- <Link>Updates\PackageType.cs</Link>
- </Compile>
<Compile Include="..\MediaBrowser.Model\Updates\PackageVersionClass.cs">
<Link>Updates\PackageVersionClass.cs</Link>
</Compile>
diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
index ad3811646..3ad40e2f5 100644
--- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
+++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
@@ -132,24 +132,12 @@
<Compile Include="..\MediaBrowser.Model\Channels\ChannelQuery.cs">
<Link>Channels\ChannelQuery.cs</Link>
</Compile>
- <Compile Include="..\MediaBrowser.Model\Chapters\ChapterProviderInfo.cs">
- <Link>Chapters\ChapterProviderInfo.cs</Link>
- </Compile>
- <Compile Include="..\MediaBrowser.Model\Chapters\RemoteChapterInfo.cs">
- <Link>Chapters\RemoteChapterInfo.cs</Link>
- </Compile>
- <Compile Include="..\MediaBrowser.Model\Chapters\RemoteChapterResult.cs">
- <Link>Chapters\RemoteChapterResult.cs</Link>
- </Compile>
<Compile Include="..\MediaBrowser.Model\Collections\CollectionCreationResult.cs">
<Link>Collections\CollectionCreationResult.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\Configuration\AccessSchedule.cs">
<Link>Configuration\AccessSchedule.cs</Link>
</Compile>
- <Compile Include="..\MediaBrowser.Model\Configuration\AutoOnOff.cs">
- <Link>Configuration\AutoOnOff.cs</Link>
- </Compile>
<Compile Include="..\MediaBrowser.Model\Configuration\BaseApplicationConfiguration.cs">
<Link>Configuration\BaseApplicationConfiguration.cs</Link>
</Compile>
@@ -1140,9 +1128,6 @@
<Compile Include="..\MediaBrowser.Model\Updates\PackageTargetSystem.cs">
<Link>Updates\PackageTargetSystem.cs</Link>
</Compile>
- <Compile Include="..\MediaBrowser.Model\Updates\PackageType.cs">
- <Link>Updates\PackageType.cs</Link>
- </Compile>
<Compile Include="..\MediaBrowser.Model\Updates\PackageVersionClass.cs">
<Link>Updates\PackageVersionClass.cs</Link>
</Compile>
@@ -1193,4 +1178,4 @@
<Target Name="AfterBuild">
</Target>
-->
-</Project>
+</Project> \ No newline at end of file
diff --git a/MediaBrowser.Model/Chapters/ChapterProviderInfo.cs b/MediaBrowser.Model/Chapters/ChapterProviderInfo.cs
deleted file mode 100644
index 570407c57..000000000
--- a/MediaBrowser.Model/Chapters/ChapterProviderInfo.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-namespace MediaBrowser.Model.Chapters
-{
- public class ChapterProviderInfo
- {
- public string Name { get; set; }
- public string Id { get; set; }
- }
-} \ No newline at end of file
diff --git a/MediaBrowser.Model/Chapters/RemoteChapterInfo.cs b/MediaBrowser.Model/Chapters/RemoteChapterInfo.cs
deleted file mode 100644
index f2674c842..000000000
--- a/MediaBrowser.Model/Chapters/RemoteChapterInfo.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-
-namespace MediaBrowser.Model.Chapters
-{
- public class RemoteChapterInfo
- {
- /// <summary>
- /// Gets or sets the start position ticks.
- /// </summary>
- /// <value>The start position ticks.</value>
- public long StartPositionTicks { get; set; }
-
- /// <summary>
- /// Gets or sets the name.
- /// </summary>
- /// <value>The name.</value>
- public string Name { get; set; }
- }
-}
diff --git a/MediaBrowser.Model/Chapters/RemoteChapterResult.cs b/MediaBrowser.Model/Chapters/RemoteChapterResult.cs
deleted file mode 100644
index 425c3ded8..000000000
--- a/MediaBrowser.Model/Chapters/RemoteChapterResult.cs
+++ /dev/null
@@ -1,48 +0,0 @@
-
-namespace MediaBrowser.Model.Chapters
-{
- public class RemoteChapterResult
- {
- /// <summary>
- /// Gets or sets the identifier.
- /// </summary>
- /// <value>The identifier.</value>
- public string Id { get; set; }
-
- /// <summary>
- /// Gets or sets the run time ticks.
- /// </summary>
- /// <value>The run time ticks.</value>
- public long? RunTimeTicks { get; set; }
-
- /// <summary>
- /// Gets or sets the name.
- /// </summary>
- /// <value>The name.</value>
- public string Name { get; set; }
-
- /// <summary>
- /// Gets or sets the name of the provider.
- /// </summary>
- /// <value>The name of the provider.</value>
- public string ProviderName { get; set; }
-
- /// <summary>
- /// Gets or sets the community rating.
- /// </summary>
- /// <value>The community rating.</value>
- public float? CommunityRating { get; set; }
-
- /// <summary>
- /// Gets or sets the chapter count.
- /// </summary>
- /// <value>The chapter count.</value>
- public int? ChapterCount { get; set; }
-
- /// <summary>
- /// Gets or sets the name of the three letter iso language.
- /// </summary>
- /// <value>The name of the three letter iso language.</value>
- public string ThreeLetterISOLanguageName { get; set; }
- }
-}
diff --git a/MediaBrowser.Model/Configuration/AutoOnOff.cs b/MediaBrowser.Model/Configuration/AutoOnOff.cs
deleted file mode 100644
index e911a0ff1..000000000
--- a/MediaBrowser.Model/Configuration/AutoOnOff.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-
-namespace MediaBrowser.Model.Configuration
-{
- public enum AutoOnOff
- {
- Auto,
- Enabled,
- Disabled
- }
-}
diff --git a/MediaBrowser.Model/Configuration/ChapterOptions.cs b/MediaBrowser.Model/Configuration/ChapterOptions.cs
index f9ff6b4f9..1c2b0c527 100644
--- a/MediaBrowser.Model/Configuration/ChapterOptions.cs
+++ b/MediaBrowser.Model/Configuration/ChapterOptions.cs
@@ -6,20 +6,6 @@
public bool EnableEpisodeChapterImageExtraction { get; set; }
public bool EnableOtherVideoChapterImageExtraction { get; set; }
- public bool DownloadMovieChapters { get; set; }
- public bool DownloadEpisodeChapters { get; set; }
-
- public string[] FetcherOrder { get; set; }
- public string[] DisabledFetchers { get; set; }
-
public bool ExtractDuringLibraryScan { get; set; }
-
- public ChapterOptions()
- {
- DownloadMovieChapters = true;
-
- DisabledFetchers = new string[] { };
- FetcherOrder = new string[] { };
- }
}
} \ No newline at end of file
diff --git a/MediaBrowser.Model/Configuration/DlnaOptions.cs b/MediaBrowser.Model/Configuration/DlnaOptions.cs
index 6eaf01c4a..71a24d707 100644
--- a/MediaBrowser.Model/Configuration/DlnaOptions.cs
+++ b/MediaBrowser.Model/Configuration/DlnaOptions.cs
@@ -10,7 +10,6 @@ namespace MediaBrowser.Model.Configuration
public int ClientDiscoveryIntervalSeconds { get; set; }
public int BlastAliveMessageIntervalSeconds { get; set; }
public string DefaultUserId { get; set; }
- public bool EnableMovieFolders { get; set; }
public DlnaOptions()
{
diff --git a/MediaBrowser.Model/Configuration/LibraryOptions.cs b/MediaBrowser.Model/Configuration/LibraryOptions.cs
index 551363223..5f8c77ccd 100644
--- a/MediaBrowser.Model/Configuration/LibraryOptions.cs
+++ b/MediaBrowser.Model/Configuration/LibraryOptions.cs
@@ -8,11 +8,24 @@
public int SchemaVersion { get; set; }
public bool EnableChapterImageExtraction { get; set; }
public bool ExtractChapterImagesDuringLibraryScan { get; set; }
+ public bool DownloadImagesInAdvance { get; set; }
+ public MediaPathInfo[] PathInfos { get; set; }
+
+ public bool SaveLocalMetadata { get; set; }
+ public bool EnableInternetProviders { get; set; }
public LibraryOptions()
{
EnablePhotos = true;
EnableRealtimeMonitor = true;
+ PathInfos = new MediaPathInfo[] { };
+ EnableInternetProviders = true;
}
}
+
+ public class MediaPathInfo
+ {
+ public string Path { get; set; }
+ public string NetworkPath { get; set; }
+ }
}
diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
index 40ac4be8a..a1e5637a4 100644
--- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
@@ -73,6 +73,7 @@ namespace MediaBrowser.Model.Configuration
/// </summary>
/// <value>The metadata path.</value>
public string MetadataPath { get; set; }
+ public string MetadataNetworkPath { get; set; }
public string LastVersion { get; set; }
@@ -167,6 +168,8 @@ namespace MediaBrowser.Model.Configuration
public MetadataOptions[] MetadataOptions { get; set; }
public bool EnableAutomaticRestart { get; set; }
+ public bool SkipDeserializationForBasicTypes { get; set; }
+ public bool SkipDeserializationForPrograms { get; set; }
public PathSubstitution[] PathSubstitutions { get; set; }
@@ -183,8 +186,6 @@ namespace MediaBrowser.Model.Configuration
public int RemoteClientBitrateLimit { get; set; }
- public AutoOnOff EnableLibraryMonitor { get; set; }
-
public int SharingExpirationDays { get; set; }
public string[] Migrations { get; set; }
@@ -193,8 +194,6 @@ namespace MediaBrowser.Model.Configuration
public int SchemaVersion { get; set; }
public int SqliteCacheSize { get; set; }
- public bool DownloadImagesInAdvance { get; set; }
-
public bool EnableAnonymousUsageReporting { get; set; }
public bool EnableStandaloneMusicKeys { get; set; }
public bool EnableLocalizedGuids { get; set; }
@@ -206,6 +205,7 @@ namespace MediaBrowser.Model.Configuration
public string[] CodecsUsed { get; set; }
public bool EnableChannelView { get; set; }
public bool EnableExternalContentInSuggestions { get; set; }
+ public bool EnableSimpleArtistDetection { get; set; }
public int ImageExtractionTimeoutMs { get; set; }
/// <summary>
@@ -244,7 +244,6 @@ namespace MediaBrowser.Model.Configuration
// 5 minutes
MinResumeDurationSeconds = 300;
- EnableLibraryMonitor = AutoOnOff.Auto;
LibraryMonitorDelay = 60;
EnableInternetProviders = true;
@@ -436,7 +435,8 @@ namespace MediaBrowser.Model.Configuration
Limit = 0,
Type = ImageType.Disc
}
- }
+ },
+ DisabledMetadataFetchers = new []{ "TheAudioDB" }
},
new MetadataOptions(1, 1280)
@@ -474,7 +474,8 @@ namespace MediaBrowser.Model.Configuration
Limit = 0,
Type = ImageType.Logo
}
- }
+ },
+ DisabledMetadataFetchers = new []{ "TheAudioDB" }
},
new MetadataOptions(1, 1280)
diff --git a/MediaBrowser.Model/Dlna/ConditionProcessor.cs b/MediaBrowser.Model/Dlna/ConditionProcessor.cs
index 69f1369dc..ec13cacfa 100644
--- a/MediaBrowser.Model/Dlna/ConditionProcessor.cs
+++ b/MediaBrowser.Model/Dlna/ConditionProcessor.cs
@@ -20,12 +20,15 @@ namespace MediaBrowser.Model.Dlna
int? refFrames,
int? numVideoStreams,
int? numAudioStreams,
- string videoCodecTag)
+ string videoCodecTag,
+ bool? isAvc)
{
switch (condition.Property)
{
case ProfileConditionValue.IsAnamorphic:
return IsConditionSatisfied(condition, isAnamorphic);
+ case ProfileConditionValue.IsAvc:
+ return IsConditionSatisfied(condition, isAvc);
case ProfileConditionValue.VideoFramerate:
return IsConditionSatisfied(condition, videoFramerate);
case ProfileConditionValue.VideoLevel:
diff --git a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs b/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs
index c4b3383a2..4a16a2780 100644
--- a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs
+++ b/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs
@@ -118,7 +118,8 @@ namespace MediaBrowser.Model.Dlna
int? refFrames,
int? numVideoStreams,
int? numAudioStreams,
- string videoCodecTag)
+ string videoCodecTag,
+ bool? isAvc)
{
// first bit means Time based seek supported, second byte range seek supported (not sure about the order now), so 01 = only byte seek, 10 = time based, 11 = both, 00 = none
string orgOp = ";DLNA.ORG_OP=" + DlnaMaps.GetOrgOpValue(runtimeTicks.HasValue, isDirectStream, transcodeSeekInfo);
@@ -159,7 +160,8 @@ namespace MediaBrowser.Model.Dlna
refFrames,
numVideoStreams,
numAudioStreams,
- videoCodecTag);
+ videoCodecTag,
+ isAvc);
List<string> orgPnValues = new List<string>();
diff --git a/MediaBrowser.Model/Dlna/DeviceProfile.cs b/MediaBrowser.Model/Dlna/DeviceProfile.cs
index d6a322322..884a9f29d 100644
--- a/MediaBrowser.Model/Dlna/DeviceProfile.cs
+++ b/MediaBrowser.Model/Dlna/DeviceProfile.cs
@@ -285,7 +285,8 @@ namespace MediaBrowser.Model.Dlna
int? refFrames,
int? numVideoStreams,
int? numAudioStreams,
- string videoCodecTag)
+ string videoCodecTag,
+ bool? isAvc)
{
container = StringHelper.TrimStart(container ?? string.Empty, '.');
@@ -319,7 +320,7 @@ namespace MediaBrowser.Model.Dlna
var anyOff = false;
foreach (ProfileCondition c in i.Conditions)
{
- if (!conditionProcessor.IsVideoConditionSatisfied(c, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, refFrames, numVideoStreams, numAudioStreams, videoCodecTag))
+ if (!conditionProcessor.IsVideoConditionSatisfied(c, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc))
{
anyOff = true;
break;
diff --git a/MediaBrowser.Model/Dlna/ProfileConditionValue.cs b/MediaBrowser.Model/Dlna/ProfileConditionValue.cs
index c17a09c3f..7e2002f17 100644
--- a/MediaBrowser.Model/Dlna/ProfileConditionValue.cs
+++ b/MediaBrowser.Model/Dlna/ProfileConditionValue.cs
@@ -20,6 +20,7 @@
NumAudioStreams = 16,
NumVideoStreams = 17,
IsSecondaryAudio = 18,
- VideoCodecTag = 19
+ VideoCodecTag = 19,
+ IsAvc = 20
}
} \ No newline at end of file
diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs
index bd186b5e5..4b225d576 100644
--- a/MediaBrowser.Model/Dlna/StreamBuilder.cs
+++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs
@@ -541,6 +541,7 @@ namespace MediaBrowser.Model.Dlna
float? videoFramerate = videoStream == null ? null : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate;
bool? isAnamorphic = videoStream == null ? null : videoStream.IsAnamorphic;
string videoCodecTag = videoStream == null ? null : videoStream.CodecTag;
+ bool? isAvc = videoStream == null ? null : videoStream.IsAVC;
TransportStreamTimestamp? timestamp = videoStream == null ? TransportStreamTimestamp.None : item.Timestamp;
int? packetLength = videoStream == null ? null : videoStream.PacketLength;
@@ -549,7 +550,7 @@ namespace MediaBrowser.Model.Dlna
int? numAudioStreams = item.GetStreamCount(MediaStreamType.Audio);
int? numVideoStreams = item.GetStreamCount(MediaStreamType.Video);
- if (!conditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, refFrames, numVideoStreams, numAudioStreams, videoCodecTag))
+ if (!conditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc))
{
LogConditionFailure(options.Profile, "VideoCodecProfile", applyCondition, item);
applyConditions = false;
@@ -718,6 +719,7 @@ namespace MediaBrowser.Model.Dlna
float? videoFramerate = videoStream == null ? null : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate;
bool? isAnamorphic = videoStream == null ? null : videoStream.IsAnamorphic;
string videoCodecTag = videoStream == null ? null : videoStream.CodecTag;
+ bool? isAvc = videoStream == null ? null : videoStream.IsAVC;
int? audioBitrate = audioStream == null ? null : audioStream.BitRate;
int? audioChannels = audioStream == null ? null : audioStream.Channels;
@@ -733,7 +735,7 @@ namespace MediaBrowser.Model.Dlna
// Check container conditions
foreach (ProfileCondition i in conditions)
{
- if (!conditionProcessor.IsVideoConditionSatisfied(i, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, refFrames, numVideoStreams, numAudioStreams, videoCodecTag))
+ if (!conditionProcessor.IsVideoConditionSatisfied(i, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc))
{
LogConditionFailure(profile, "VideoContainerProfile", i, mediaSource);
@@ -760,7 +762,7 @@ namespace MediaBrowser.Model.Dlna
bool applyConditions = true;
foreach (ProfileCondition applyCondition in i.ApplyConditions)
{
- if (!conditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, refFrames, numVideoStreams, numAudioStreams, videoCodecTag))
+ if (!conditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc))
{
LogConditionFailure(profile, "VideoCodecProfile", applyCondition, mediaSource);
applyConditions = false;
@@ -780,7 +782,7 @@ namespace MediaBrowser.Model.Dlna
foreach (ProfileCondition i in conditions)
{
- if (!conditionProcessor.IsVideoConditionSatisfied(i, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, refFrames, numVideoStreams, numAudioStreams, videoCodecTag))
+ if (!conditionProcessor.IsVideoConditionSatisfied(i, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc))
{
LogConditionFailure(profile, "VideoCodecProfile", i, mediaSource);
diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs
index ac024f00b..c9cb873e1 100644
--- a/MediaBrowser.Model/Dlna/StreamInfo.cs
+++ b/MediaBrowser.Model/Dlna/StreamInfo.cs
@@ -215,13 +215,26 @@ namespace MediaBrowser.Model.Dlna
list.Add(new NameValuePair("MaxWidth", item.MaxWidth.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxWidth.Value) : string.Empty));
list.Add(new NameValuePair("MaxHeight", item.MaxHeight.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxHeight.Value) : string.Empty));
- if (StringHelper.EqualsIgnoreCase(item.SubProtocol, "hls"))
+ var forceStartPosition = false;
+ long startPositionTicks = item.StartPositionTicks;
+ //if (item.MediaSource.DateLiveStreamOpened.HasValue && startPositionTicks == 0)
+ //{
+ // var elapsed = DateTime.UtcNow - item.MediaSource.DateLiveStreamOpened.Value;
+ // elapsed -= TimeSpan.FromSeconds(20);
+ // if (elapsed.TotalSeconds >= 0)
+ // {
+ // startPositionTicks = elapsed.Ticks + startPositionTicks;
+ // forceStartPosition = true;
+ // }
+ //}
+
+ if (StringHelper.EqualsIgnoreCase(item.SubProtocol, "hls") && !forceStartPosition)
{
list.Add(new NameValuePair("StartTimeTicks", string.Empty));
}
else
{
- list.Add(new NameValuePair("StartTimeTicks", StringHelper.ToStringCultureInvariant(item.StartPositionTicks)));
+ list.Add(new NameValuePair("StartTimeTicks", StringHelper.ToStringCultureInvariant(startPositionTicks)));
}
list.Add(new NameValuePair("Level", item.VideoLevel.HasValue ? StringHelper.ToStringCultureInvariant(item.VideoLevel.Value) : string.Empty));
@@ -663,6 +676,19 @@ namespace MediaBrowser.Model.Dlna
}
}
+ public bool? IsTargetAVC
+ {
+ get
+ {
+ if (IsDirectStream)
+ {
+ return TargetVideoStream == null ? null : TargetVideoStream.IsAVC;
+ }
+
+ return true;
+ }
+ }
+
public int? TargetWidth
{
get
diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs
index 348a781ae..9267222ad 100644
--- a/MediaBrowser.Model/Dto/BaseItemDto.cs
+++ b/MediaBrowser.Model/Dto/BaseItemDto.cs
@@ -169,6 +169,8 @@ namespace MediaBrowser.Model.Dto
/// <value>The game system.</value>
public string GameSystem { get; set; }
+ public string[] ProductionLocations { get; set; }
+
/// <summary>
/// Gets or sets the critic rating summary.
/// </summary>
@@ -788,12 +790,6 @@ namespace MediaBrowser.Model.Dto
public string HomePageUrl { get; set; }
/// <summary>
- /// Gets or sets the production locations.
- /// </summary>
- /// <value>The production locations.</value>
- public List<string> ProductionLocations { get; set; }
-
- /// <summary>
/// Gets or sets the budget.
/// </summary>
/// <value>The budget.</value>
@@ -826,6 +822,7 @@ namespace MediaBrowser.Model.Dto
/// </summary>
/// <value>The series count.</value>
public int? SeriesCount { get; set; }
+ public int? ProgramCount { get; set; }
/// <summary>
/// Gets or sets the episode count.
/// </summary>
diff --git a/MediaBrowser.Model/Dto/ItemCounts.cs b/MediaBrowser.Model/Dto/ItemCounts.cs
index 66c3dfebc..8ceb3a86b 100644
--- a/MediaBrowser.Model/Dto/ItemCounts.cs
+++ b/MediaBrowser.Model/Dto/ItemCounts.cs
@@ -26,6 +26,7 @@
/// <value>The game count.</value>
public int GameCount { get; set; }
public int ArtistCount { get; set; }
+ public int ProgramCount { get; set; }
/// <summary>
/// Gets or sets the game system count.
/// </summary>
diff --git a/MediaBrowser.Model/Dto/MediaSourceInfo.cs b/MediaBrowser.Model/Dto/MediaSourceInfo.cs
index bb07d9cb6..8149e3ff5 100644
--- a/MediaBrowser.Model/Dto/MediaSourceInfo.cs
+++ b/MediaBrowser.Model/Dto/MediaSourceInfo.cs
@@ -1,4 +1,5 @@
-using MediaBrowser.Model.Entities;
+using System;
+using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.MediaInfo;
using System.Collections.Generic;
@@ -26,7 +27,7 @@ namespace MediaBrowser.Model.Dto
public bool SupportsTranscoding { get; set; }
public bool SupportsDirectStream { get; set; }
public bool SupportsDirectPlay { get; set; }
-
+ public bool IsInfiniteStream { get; set; }
public bool RequiresOpening { get; set; }
public string OpenToken { get; set; }
public bool RequiresClosing { get; set; }
@@ -52,7 +53,7 @@ namespace MediaBrowser.Model.Dto
public string TranscodingUrl { get; set; }
public string TranscodingSubProtocol { get; set; }
public string TranscodingContainer { get; set; }
-
+
public MediaSourceInfo()
{
Formats = new List<string>();
diff --git a/MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs b/MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs
index 4d863c6eb..7e93a130b 100644
--- a/MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs
+++ b/MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs
@@ -10,6 +10,8 @@ namespace MediaBrowser.Model.LiveTv
/// </summary>
public string Id { get; set; }
+ public string Type { get; set; }
+
/// <summary>
/// Gets or sets the server identifier.
/// </summary>
@@ -105,5 +107,6 @@ namespace MediaBrowser.Model.LiveTv
/// </summary>
/// <value><c>true</c> if this instance is post padding required; otherwise, <c>false</c>.</value>
public bool IsPostPaddingRequired { get; set; }
+ public KeepUntil KeepUntil { get; set; }
}
} \ No newline at end of file
diff --git a/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs b/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs
index 0ece1e4a0..4505b80a0 100644
--- a/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs
+++ b/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs
@@ -1,4 +1,5 @@
-
+using MediaBrowser.Model.Entities;
+
namespace MediaBrowser.Model.LiveTv
{
/// <summary>
@@ -61,9 +62,42 @@ namespace MediaBrowser.Model.LiveTv
public bool AddCurrentProgram { get; set; }
public bool EnableUserData { get; set; }
+ /// <summary>
+ /// Used to specific whether to return news or not
+ /// </summary>
+ /// <remarks>If set to null, all programs will be returned</remarks>
+ public bool? IsNews { get; set; }
+
+ /// <summary>
+ /// Used to specific whether to return movies or not
+ /// </summary>
+ /// <remarks>If set to null, all programs will be returned</remarks>
+ public bool? IsMovie { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is kids.
+ /// </summary>
+ /// <value><c>null</c> if [is kids] contains no value, <c>true</c> if [is kids]; otherwise, <c>false</c>.</value>
+ public bool? IsKids { get; set; }
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is sports.
+ /// </summary>
+ /// <value><c>null</c> if [is sports] contains no value, <c>true</c> if [is sports]; otherwise, <c>false</c>.</value>
+ public bool? IsSports { get; set; }
+ public bool? IsSeries { get; set; }
+
+ public string[] SortBy { get; set; }
+
+ /// <summary>
+ /// The sort order to return results with
+ /// </summary>
+ /// <value>The sort order.</value>
+ public SortOrder? SortOrder { get; set; }
+
public LiveTvChannelQuery()
{
EnableUserData = true;
+ SortBy = new string[] { };
}
}
}
diff --git a/MediaBrowser.Model/LiveTv/ProgramQuery.cs b/MediaBrowser.Model/LiveTv/ProgramQuery.cs
index bf459237a..ad57d1473 100644
--- a/MediaBrowser.Model/LiveTv/ProgramQuery.cs
+++ b/MediaBrowser.Model/LiveTv/ProgramQuery.cs
@@ -41,6 +41,7 @@ namespace MediaBrowser.Model.LiveTv
/// </summary>
/// <value>The user identifier.</value>
public string UserId { get; set; }
+ public string SeriesTimerId { get; set; }
/// <summary>
/// The earliest date for which a program starts to return
@@ -63,6 +64,12 @@ namespace MediaBrowser.Model.LiveTv
public DateTime? MaxEndDate { get; set; }
/// <summary>
+ /// Used to specific whether to return news or not
+ /// </summary>
+ /// <remarks>If set to null, all programs will be returned</remarks>
+ public bool? IsNews { get; set; }
+
+ /// <summary>
/// Used to specific whether to return movies or not
/// </summary>
/// <remarks>If set to null, all programs will be returned</remarks>
@@ -83,6 +90,7 @@ namespace MediaBrowser.Model.LiveTv
/// Skips over a given number of items within the results. Use for paging.
/// </summary>
public int? StartIndex { get; set; }
+ public bool? IsSeries { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this instance has aired.
diff --git a/MediaBrowser.Model/LiveTv/RecommendedProgramQuery.cs b/MediaBrowser.Model/LiveTv/RecommendedProgramQuery.cs
index 0e6d081a1..4bc506bf6 100644
--- a/MediaBrowser.Model/LiveTv/RecommendedProgramQuery.cs
+++ b/MediaBrowser.Model/LiveTv/RecommendedProgramQuery.cs
@@ -49,7 +49,13 @@ namespace MediaBrowser.Model.LiveTv
/// Gets or sets a value indicating whether this instance is movie.
/// </summary>
/// <value><c>null</c> if [is movie] contains no value, <c>true</c> if [is movie]; otherwise, <c>false</c>.</value>
+ public bool? IsNews { get; set; }
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is movie.
+ /// </summary>
+ /// <value><c>null</c> if [is movie] contains no value, <c>true</c> if [is movie]; otherwise, <c>false</c>.</value>
public bool? IsMovie { get; set; }
+ public bool? IsSeries { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this instance is kids.
/// </summary>
diff --git a/MediaBrowser.Model/LiveTv/RecordingQuery.cs b/MediaBrowser.Model/LiveTv/RecordingQuery.cs
index 0ba5f1779..265aad335 100644
--- a/MediaBrowser.Model/LiveTv/RecordingQuery.cs
+++ b/MediaBrowser.Model/LiveTv/RecordingQuery.cs
@@ -68,6 +68,7 @@ namespace MediaBrowser.Model.LiveTv
/// <value>The fields.</value>
public ItemFields[] Fields { get; set; }
public bool? EnableImages { get; set; }
+ public bool? IsNews { get; set; }
public bool? IsMovie { get; set; }
public bool? IsSeries { get; set; }
public bool? IsKids { get; set; }
diff --git a/MediaBrowser.Model/LiveTv/RecordingStatus.cs b/MediaBrowser.Model/LiveTv/RecordingStatus.cs
index 7ab716c4d..571ff9c99 100644
--- a/MediaBrowser.Model/LiveTv/RecordingStatus.cs
+++ b/MediaBrowser.Model/LiveTv/RecordingStatus.cs
@@ -7,7 +7,6 @@ namespace MediaBrowser.Model.LiveTv
Scheduled,
InProgress,
Completed,
- Aborted,
Cancelled,
ConflictedOk,
ConflictedNotOk,
diff --git a/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs b/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs
index 4b88e42f3..997b090ff 100644
--- a/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs
+++ b/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs
@@ -12,18 +12,29 @@ namespace MediaBrowser.Model.LiveTv
[DebuggerDisplay("Name = {Name}")]
public class SeriesTimerInfoDto : BaseTimerInfoDto
{
+ public SeriesTimerInfoDto()
+ {
+ ImageTags = new Dictionary<ImageType, string>();
+ Days = new List<DayOfWeek>();
+ Type = "SeriesTimer";
+ }
+
/// <summary>
/// Gets or sets a value indicating whether [record any time].
/// </summary>
/// <value><c>true</c> if [record any time]; otherwise, <c>false</c>.</value>
public bool RecordAnyTime { get; set; }
+ public bool SkipEpisodesInLibrary { get; set; }
+
/// <summary>
/// Gets or sets a value indicating whether [record any channel].
/// </summary>
/// <value><c>true</c> if [record any channel]; otherwise, <c>false</c>.</value>
public bool RecordAnyChannel { get; set; }
+ public int KeepUpTo { get; set; }
+
/// <summary>
/// Gets or sets a value indicating whether [record new only].
/// </summary>
@@ -58,10 +69,36 @@ namespace MediaBrowser.Model.LiveTv
get { return ImageTags != null && ImageTags.ContainsKey(ImageType.Primary); }
}
- public SeriesTimerInfoDto()
- {
- ImageTags = new Dictionary<ImageType, string>();
- Days = new List<DayOfWeek>();
- }
+ /// <summary>
+ /// Gets or sets the parent thumb item id.
+ /// </summary>
+ /// <value>The parent thumb item id.</value>
+ public string ParentThumbItemId { get; set; }
+
+ /// <summary>
+ /// Gets or sets the parent thumb image tag.
+ /// </summary>
+ /// <value>The parent thumb image tag.</value>
+ public string ParentThumbImageTag { get; set; }
+
+ /// <summary>
+ /// Gets or sets the parent primary image item identifier.
+ /// </summary>
+ /// <value>The parent primary image item identifier.</value>
+ public string ParentPrimaryImageItemId { get; set; }
+
+ /// <summary>
+ /// Gets or sets the parent primary image tag.
+ /// </summary>
+ /// <value>The parent primary image tag.</value>
+ public string ParentPrimaryImageTag { get; set; }
+ }
+
+ public enum KeepUntil
+ {
+ UntilDeleted,
+ UntilSpaceNeeded,
+ UntilWatched,
+ UntilDate
}
}
diff --git a/MediaBrowser.Model/LiveTv/TimerInfoDto.cs b/MediaBrowser.Model/LiveTv/TimerInfoDto.cs
index a95678fae..d1aa3118f 100644
--- a/MediaBrowser.Model/LiveTv/TimerInfoDto.cs
+++ b/MediaBrowser.Model/LiveTv/TimerInfoDto.cs
@@ -27,8 +27,6 @@ namespace MediaBrowser.Model.LiveTv
/// <value>The external series timer identifier.</value>
public string ExternalSeriesTimerId { get; set; }
- public string Type { get; set; }
-
/// <summary>
/// Gets or sets the run time ticks.
/// </summary>
diff --git a/MediaBrowser.Model/LiveTv/TimerQuery.cs b/MediaBrowser.Model/LiveTv/TimerQuery.cs
index 87b6b89ac..310dc486f 100644
--- a/MediaBrowser.Model/LiveTv/TimerQuery.cs
+++ b/MediaBrowser.Model/LiveTv/TimerQuery.cs
@@ -15,5 +15,7 @@
public string SeriesTimerId { get; set; }
public bool? IsActive { get; set; }
+
+ public bool? IsScheduled { get; set; }
}
} \ No newline at end of file
diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj
index db70b8606..b9b920588 100644
--- a/MediaBrowser.Model/MediaBrowser.Model.csproj
+++ b/MediaBrowser.Model/MediaBrowser.Model.csproj
@@ -84,12 +84,8 @@
<Compile Include="Channels\ChannelMediaContentType.cs" />
<Compile Include="Channels\ChannelMediaType.cs" />
<Compile Include="Channels\ChannelQuery.cs" />
- <Compile Include="Chapters\ChapterProviderInfo.cs" />
- <Compile Include="Chapters\RemoteChapterInfo.cs" />
- <Compile Include="Chapters\RemoteChapterResult.cs" />
<Compile Include="Collections\CollectionCreationResult.cs" />
<Compile Include="Configuration\AccessSchedule.cs" />
- <Compile Include="Configuration\AutoOnOff.cs" />
<Compile Include="Configuration\ChannelOptions.cs" />
<Compile Include="Configuration\ChapterOptions.cs" />
<Compile Include="Configuration\CinemaModeConfiguration.cs" />
@@ -400,7 +396,6 @@
<Compile Include="Updates\CheckForUpdateResult.cs" />
<Compile Include="Updates\PackageTargetSystem.cs" />
<Compile Include="Updates\InstallationInfo.cs" />
- <Compile Include="Updates\PackageType.cs" />
<Compile Include="Updates\PackageVersionClass.cs" />
<Compile Include="Entities\EmptyRequestResult.cs" />
<Compile Include="Configuration\UserConfiguration.cs" />
diff --git a/MediaBrowser.Model/MediaInfo/MediaProtocol.cs b/MediaBrowser.Model/MediaInfo/MediaProtocol.cs
index 880310814..1474e6d96 100644
--- a/MediaBrowser.Model/MediaInfo/MediaProtocol.cs
+++ b/MediaBrowser.Model/MediaInfo/MediaProtocol.cs
@@ -5,6 +5,7 @@ namespace MediaBrowser.Model.MediaInfo
File = 0,
Http = 1,
Rtmp = 2,
- Rtsp = 3
+ Rtsp = 3,
+ Udp = 4
}
} \ No newline at end of file
diff --git a/MediaBrowser.Model/Notifications/NotificationType.cs b/MediaBrowser.Model/Notifications/NotificationType.cs
index f5e3624f0..eefd15808 100644
--- a/MediaBrowser.Model/Notifications/NotificationType.cs
+++ b/MediaBrowser.Model/Notifications/NotificationType.cs
@@ -16,7 +16,6 @@ namespace MediaBrowser.Model.Notifications
PluginUpdateInstalled,
PluginUninstalled,
NewLibraryContent,
- NewLibraryContentMultiple,
ServerRestartRequired,
TaskFailed,
CameraImageUploaded,
diff --git a/MediaBrowser.Model/Providers/RemoteSearchResult.cs b/MediaBrowser.Model/Providers/RemoteSearchResult.cs
index 8c9e116d6..72b6632e4 100644
--- a/MediaBrowser.Model/Providers/RemoteSearchResult.cs
+++ b/MediaBrowser.Model/Providers/RemoteSearchResult.cs
@@ -32,7 +32,8 @@ namespace MediaBrowser.Model.Providers
public string SearchProviderName { get; set; }
public string GameSystem { get; set; }
-
+ public string Overview { get; set; }
+
public RemoteSearchResult()
{
ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
diff --git a/MediaBrowser.Model/Querying/ItemFields.cs b/MediaBrowser.Model/Querying/ItemFields.cs
index 0400e374a..dfca9e771 100644
--- a/MediaBrowser.Model/Querying/ItemFields.cs
+++ b/MediaBrowser.Model/Querying/ItemFields.cs
@@ -130,6 +130,8 @@
/// </summary>
Metascore,
+ OfficialRatingDescription,
+
OriginalTitle,
/// <summary>
@@ -258,6 +260,8 @@
/// <summary>
/// The service name
/// </summary>
- ServiceName
+ ServiceName,
+ ThemeSongIds,
+ ThemeVideoIds
}
}
diff --git a/MediaBrowser.Model/System/SystemInfo.cs b/MediaBrowser.Model/System/SystemInfo.cs
index c4d056a61..022e03baf 100644
--- a/MediaBrowser.Model/System/SystemInfo.cs
+++ b/MediaBrowser.Model/System/SystemInfo.cs
@@ -34,6 +34,8 @@ namespace MediaBrowser.Model.System
/// <value>The mac address.</value>
public string MacAddress { get; set; }
+ public string PackageName { get; set; }
+
/// <summary>
/// Gets or sets a value indicating whether this instance has pending restart.
/// </summary>
diff --git a/MediaBrowser.Model/Updates/PackageInfo.cs b/MediaBrowser.Model/Updates/PackageInfo.cs
index 191deeb8f..208d5b784 100644
--- a/MediaBrowser.Model/Updates/PackageInfo.cs
+++ b/MediaBrowser.Model/Updates/PackageInfo.cs
@@ -12,7 +12,7 @@ namespace MediaBrowser.Model.Updates
/// The internal id of this package.
/// </summary>
/// <value>The id.</value>
- public int id { get; set; }
+ public string id { get; set; }
/// <summary>
/// Gets or sets the name.
@@ -66,7 +66,7 @@ namespace MediaBrowser.Model.Updates
/// Gets or sets the type.
/// </summary>
/// <value>The type.</value>
- public PackageType type { get; set; }
+ public string type { get; set; }
/// <summary>
/// Gets or sets the target filename.
@@ -127,7 +127,7 @@ namespace MediaBrowser.Model.Updates
/// Gets or sets the total number of ratings for this package.
/// </summary>
/// <value>The total ratings.</value>
- public int totalRatings { get; set; }
+ public int? totalRatings { get; set; }
/// <summary>
/// Gets or sets the average rating for this package .
diff --git a/MediaBrowser.Model/Updates/PackageType.cs b/MediaBrowser.Model/Updates/PackageType.cs
deleted file mode 100644
index a00196e71..000000000
--- a/MediaBrowser.Model/Updates/PackageType.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-namespace MediaBrowser.Model.Updates
-{
- /// <summary>
- /// Enum PackageType
- /// </summary>
- public enum PackageType
- {
- /// <summary>
- /// All
- /// </summary>
- All,
- /// <summary>
- /// The system
- /// </summary>
- System,
- /// <summary>
- /// The user installed
- /// </summary>
- UserInstalled
- }
-} \ No newline at end of file
diff --git a/MediaBrowser.Mono.sln b/MediaBrowser.Mono.sln
index 3d9677fa8..6300a9559 100644
--- a/MediaBrowser.Mono.sln
+++ b/MediaBrowser.Mono.sln
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 2013
-VisualStudioVersion = 12.0.30723.0
+# Visual Studio 14
+VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Model", "MediaBrowser.Model\MediaBrowser.Model.csproj", "{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}"
EndProject
@@ -35,6 +35,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Server.Startup
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Emby.Drawing", "Emby.Drawing\Emby.Drawing.csproj", "{08FFF49B-F175-4807-A2B5-73B0EBD9F716}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mono.Nat", "Mono.Nat\Mono.Nat.csproj", "{D7453B88-2266-4805-B39B-2B5A2A33E1BA}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -203,6 +205,18 @@ Global
{08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|Any CPU.ActiveCfg = Release|Any CPU
{08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|Any CPU.Build.0 = Release|Any CPU
{08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|x86.ActiveCfg = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|x86.Build.0 = Debug|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|Any CPU.ActiveCfg = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|Any CPU.Build.0 = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|x86.ActiveCfg = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|x86.Build.0 = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|x86.ActiveCfg = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/MediaBrowser.Providers/Chapters/ChapterManager.cs b/MediaBrowser.Providers/Chapters/ChapterManager.cs
index 88811c850..87aaafb39 100644
--- a/MediaBrowser.Providers/Chapters/ChapterManager.cs
+++ b/MediaBrowser.Providers/Chapters/ChapterManager.cs
@@ -8,7 +8,6 @@ using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Chapters;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
@@ -22,7 +21,6 @@ namespace MediaBrowser.Providers.Chapters
{
public class ChapterManager : IChapterManager
{
- private IChapterProvider[] _providers;
private readonly ILibraryManager _libraryManager;
private readonly ILogger _logger;
private readonly IServerConfigurationManager _config;
@@ -36,224 +34,6 @@ namespace MediaBrowser.Providers.Chapters
_itemRepo = itemRepo;
}
- public void AddParts(IEnumerable<IChapterProvider> chapterProviders)
- {
- _providers = chapterProviders.ToArray();
- }
-
- public Task<IEnumerable<RemoteChapterResult>> Search(Video video, CancellationToken cancellationToken)
- {
- VideoContentType mediaType;
-
- if (video is Episode)
- {
- mediaType = VideoContentType.Episode;
- }
- else if (video is Movie)
- {
- mediaType = VideoContentType.Movie;
- }
- else
- {
- // These are the only supported types
- return Task.FromResult<IEnumerable<RemoteChapterResult>>(new List<RemoteChapterResult>());
- }
-
- var request = new ChapterSearchRequest
- {
- ContentType = mediaType,
- IndexNumber = video.IndexNumber,
- Language = video.GetPreferredMetadataLanguage(),
- MediaPath = video.Path,
- Name = video.Name,
- ParentIndexNumber = video.ParentIndexNumber,
- ProductionYear = video.ProductionYear,
- ProviderIds = video.ProviderIds,
- RuntimeTicks = video.RunTimeTicks,
- SearchAllProviders = false
- };
-
- var episode = video as Episode;
-
- if (episode != null)
- {
- request.IndexNumberEnd = episode.IndexNumberEnd;
- request.SeriesName = episode.SeriesName;
- }
-
- return Search(request, cancellationToken);
- }
-
- public async Task<IEnumerable<RemoteChapterResult>> Search(ChapterSearchRequest request, CancellationToken cancellationToken)
- {
- var contentType = request.ContentType;
- var providers = GetInternalProviders(false)
- .Where(i => i.SupportedMediaTypes.Contains(contentType))
- .ToList();
-
- // If not searching all, search one at a time until something is found
- if (!request.SearchAllProviders)
- {
- foreach (var provider in providers)
- {
- try
- {
- var currentResults = await Search(request, provider, cancellationToken).ConfigureAwait(false);
-
- if (currentResults.Count > 0)
- {
- return currentResults;
- }
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error downloading subtitles from {0}", ex, provider.Name);
- }
- }
- return new List<RemoteChapterResult>();
- }
-
- var tasks = providers.Select(async i =>
- {
- try
- {
- return await Search(request, i, cancellationToken).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error downloading subtitles from {0}", ex, i.Name);
- return new List<RemoteChapterResult>();
- }
- });
-
- var results = await Task.WhenAll(tasks).ConfigureAwait(false);
-
- return results.SelectMany(i => i);
- }
-
- private async Task<List<RemoteChapterResult>> Search(ChapterSearchRequest request,
- IChapterProvider provider,
- CancellationToken cancellationToken)
- {
- var searchResults = await provider.Search(request, cancellationToken).ConfigureAwait(false);
-
- var list = searchResults.ToList();
-
- foreach (var result in list)
- {
- result.Id = GetProviderId(provider.Name) + "_" + result.Id;
- result.ProviderName = provider.Name;
- }
-
- return list;
- }
-
- public Task<ChapterResponse> GetChapters(string id, CancellationToken cancellationToken)
- {
- var parts = id.Split(new[] { '_' }, 2);
-
- var provider = GetProvider(parts.First());
- id = parts.Last();
-
- return provider.GetChapters(id, cancellationToken);
- }
-
- public IEnumerable<ChapterProviderInfo> GetProviders(string itemId)
- {
- var video = _libraryManager.GetItemById(itemId) as Video;
- VideoContentType mediaType;
-
- if (video is Episode)
- {
- mediaType = VideoContentType.Episode;
- }
- else if (video is Movie)
- {
- mediaType = VideoContentType.Movie;
- }
- else
- {
- // These are the only supported types
- return new List<ChapterProviderInfo>();
- }
-
- var providers = GetInternalProviders(false)
- .Where(i => i.SupportedMediaTypes.Contains(mediaType));
-
- return GetInfos(providers);
- }
-
- public IEnumerable<ChapterProviderInfo> GetProviders()
- {
- return GetInfos(GetInternalProviders(true));
- }
-
- private IEnumerable<IChapterProvider> GetInternalProviders(bool includeDisabledProviders)
- {
- var providers = _providers;
-
- if (!includeDisabledProviders)
- {
- var options = GetConfiguration();
-
- providers = providers
- .Where(i => !options.DisabledFetchers.Contains(i.Name))
- .ToArray();
- }
-
- return providers
- .OrderBy(GetConfiguredOrder)
- .ThenBy(GetDefaultOrder)
- .ToArray();
- }
-
- private IEnumerable<ChapterProviderInfo> GetInfos(IEnumerable<IChapterProvider> providers)
- {
- return providers.Select(i => new ChapterProviderInfo
- {
- Name = i.Name,
- Id = GetProviderId(i.Name)
- });
- }
-
- private string GetProviderId(string name)
- {
- return name.ToLower().GetMD5().ToString("N");
- }
-
- private IChapterProvider GetProvider(string id)
- {
- return _providers.First(i => string.Equals(id, GetProviderId(i.Name)));
- }
-
- private int GetConfiguredOrder(IChapterProvider provider)
- {
- var options = GetConfiguration();
-
- // See if there's a user-defined order
- var index = Array.IndexOf(options.FetcherOrder, provider.Name);
-
- if (index != -1)
- {
- return index;
- }
-
- // Not configured. Just return some high number to put it at the end.
- return 100;
- }
-
- private int GetDefaultOrder(IChapterProvider provider)
- {
- var hasOrder = provider as IHasOrder;
-
- if (hasOrder != null)
- {
- return hasOrder.Order;
- }
-
- return 0;
- }
-
public IEnumerable<ChapterInfo> GetChapters(string itemId)
{
return _itemRepo.GetChapters(new Guid(itemId));
diff --git a/MediaBrowser.Providers/GameGenres/GameGenreImageProvider.cs b/MediaBrowser.Providers/GameGenres/GameGenreImageProvider.cs
index 3a532257f..b26f23715 100644
--- a/MediaBrowser.Providers/GameGenres/GameGenreImageProvider.cs
+++ b/MediaBrowser.Providers/GameGenres/GameGenreImageProvider.cs
@@ -37,7 +37,7 @@ namespace MediaBrowser.Providers.GameGenres
public static string ProviderName
{
- get { return "Media Browser Designs"; }
+ get { return "Emby Designs"; }
}
public bool Supports(IHasImages item)
@@ -137,7 +137,7 @@ namespace MediaBrowser.Providers.GameGenres
{
CancellationToken = cancellationToken,
Url = url,
- ResourcePool = GenreImageProvider.ImageDownloadResourcePool
+ BufferContent = false
});
}
}
diff --git a/MediaBrowser.Providers/Genres/GenreImageProvider.cs b/MediaBrowser.Providers/Genres/GenreImageProvider.cs
index 7c2ed00a6..954cd008e 100644
--- a/MediaBrowser.Providers/Genres/GenreImageProvider.cs
+++ b/MediaBrowser.Providers/Genres/GenreImageProvider.cs
@@ -22,8 +22,6 @@ namespace MediaBrowser.Providers.Genres
private readonly SemaphoreSlim _listResourcePool = new SemaphoreSlim(1, 1);
- public static SemaphoreSlim ImageDownloadResourcePool = new SemaphoreSlim(5, 5);
-
public GenreImageProvider(IServerConfigurationManager config, IHttpClient httpClient, IFileSystem fileSystem)
{
_config = config;
@@ -38,7 +36,7 @@ namespace MediaBrowser.Providers.Genres
public static string ProviderName
{
- get { return "Media Browser Designs"; }
+ get { return "Emby Designs"; }
}
public bool Supports(IHasImages item)
@@ -138,7 +136,7 @@ namespace MediaBrowser.Providers.Genres
{
CancellationToken = cancellationToken,
Url = url,
- ResourcePool = ImageDownloadResourcePool
+ BufferContent = false
});
}
}
diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs
index 3de330557..5203adc9d 100644
--- a/MediaBrowser.Providers/Manager/ImageSaver.cs
+++ b/MediaBrowser.Providers/Manager/ImageSaver.cs
@@ -38,6 +38,7 @@ namespace MediaBrowser.Providers.Manager
private readonly ILibraryMonitor _libraryMonitor;
private readonly IFileSystem _fileSystem;
private readonly ILogger _logger;
+ private readonly IMemoryStreamProvider _memoryStreamProvider;
/// <summary>
/// Initializes a new instance of the <see cref="ImageSaver" /> class.
@@ -46,12 +47,13 @@ namespace MediaBrowser.Providers.Manager
/// <param name="libraryMonitor">The directory watchers.</param>
/// <param name="fileSystem">The file system.</param>
/// <param name="logger">The logger.</param>
- public ImageSaver(IServerConfigurationManager config, ILibraryMonitor libraryMonitor, IFileSystem fileSystem, ILogger logger)
+ public ImageSaver(IServerConfigurationManager config, ILibraryMonitor libraryMonitor, IFileSystem fileSystem, ILogger logger, IMemoryStreamProvider memoryStreamProvider)
{
_config = config;
_libraryMonitor = libraryMonitor;
_fileSystem = fileSystem;
_logger = logger;
+ _memoryStreamProvider = memoryStreamProvider;
}
/// <summary>
@@ -124,7 +126,7 @@ namespace MediaBrowser.Providers.Manager
var retryPaths = GetSavePaths(item, type, imageIndex, mimeType, false);
// If there are more than one output paths, the stream will need to be seekable
- var memoryStream = new MemoryStream();
+ var memoryStream = _memoryStreamProvider.CreateNew();
using (source)
{
await source.CopyToAsync(memoryStream).ConfigureAwait(false);
@@ -211,6 +213,20 @@ namespace MediaBrowser.Providers.Manager
throw;
}
}
+ catch (IOException ex)
+ {
+ var retry = !string.IsNullOrWhiteSpace(retryPath) &&
+ !string.Equals(path, retryPath, StringComparison.OrdinalIgnoreCase);
+
+ if (retry)
+ {
+ _logger.Error("IOException saving to {0}. {2}. Will retry saving to {1}", path, retryPath, ex.Message);
+ }
+ else
+ {
+ throw;
+ }
+ }
source.Position = 0;
await SaveImageToLocation(source, retryPath, cancellationToken).ConfigureAwait(false);
@@ -355,7 +371,7 @@ namespace MediaBrowser.Providers.Manager
return Path.Combine(seriesFolder, imageFilename);
}
- if (item.IsInMixedFolder)
+ if (item.DetectIsInMixedFolder())
{
return GetSavePathForItemInMixedFolder(item, type, "landscape", extension);
}
@@ -431,7 +447,7 @@ namespace MediaBrowser.Providers.Manager
path = Path.Combine(Path.GetDirectoryName(item.Path), "metadata", filename + extension);
}
- else if (item.IsInMixedFolder)
+ else if (item.DetectIsInMixedFolder())
{
path = GetSavePathForItemInMixedFolder(item, type, filename, extension);
}
@@ -498,7 +514,7 @@ namespace MediaBrowser.Providers.Manager
if (imageIndex.Value == 0)
{
- if (item.IsInMixedFolder)
+ if (item.DetectIsInMixedFolder())
{
return new[] { GetSavePathForItemInMixedFolder(item, type, "fanart", extension) };
}
@@ -524,7 +540,7 @@ namespace MediaBrowser.Providers.Manager
var outputIndex = imageIndex.Value;
- if (item.IsInMixedFolder)
+ if (item.DetectIsInMixedFolder())
{
return new[] { GetSavePathForItemInMixedFolder(item, type, "fanart" + outputIndex.ToString(UsCulture), extension) };
}
@@ -567,7 +583,7 @@ namespace MediaBrowser.Providers.Manager
return new[] { Path.Combine(seasonFolder, imageFilename) };
}
- if (item.IsInMixedFolder || item is MusicVideo)
+ if (item.DetectIsInMixedFolder() || item is MusicVideo)
{
return new[] { GetSavePathForItemInMixedFolder(item, type, string.Empty, extension) };
}
diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs
index 97dd1ed4c..87da835dc 100644
--- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs
+++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs
@@ -56,7 +56,7 @@ namespace MediaBrowser.Providers.Manager
return hasChanges;
}
- public async Task<RefreshResult> RefreshImages(IHasImages item, IEnumerable<IImageProvider> imageProviders, ImageRefreshOptions refreshOptions, MetadataOptions savedOptions, CancellationToken cancellationToken)
+ public async Task<RefreshResult> RefreshImages(IHasImages item, LibraryOptions libraryOptions, IEnumerable<IImageProvider> imageProviders, ImageRefreshOptions refreshOptions, MetadataOptions savedOptions, CancellationToken cancellationToken)
{
if (refreshOptions.IsReplacingImage(ImageType.Backdrop))
{
@@ -84,7 +84,7 @@ namespace MediaBrowser.Providers.Manager
if (remoteProvider != null)
{
- await RefreshFromProvider(item, remoteProvider, refreshOptions, savedOptions, backdropLimit, screenshotLimit, downloadedImages, result, cancellationToken).ConfigureAwait(false);
+ await RefreshFromProvider(item, libraryOptions, remoteProvider, refreshOptions, savedOptions, backdropLimit, screenshotLimit, downloadedImages, result, cancellationToken).ConfigureAwait(false);
providerIds.Add(provider.GetType().FullName.GetMD5());
continue;
}
@@ -249,7 +249,7 @@ namespace MediaBrowser.Providers.Manager
/// <param name="result">The result.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
- private async Task RefreshFromProvider(IHasImages item,
+ private async Task RefreshFromProvider(IHasImages item, LibraryOptions libraryOptions,
IRemoteImageProvider provider,
ImageRefreshOptions refreshOptions,
MetadataOptions savedOptions,
@@ -293,7 +293,7 @@ namespace MediaBrowser.Providers.Manager
if (!HasImage(item, imageType) || (refreshOptions.IsReplacingImage(imageType) && !downloadedImages.Contains(imageType)))
{
minWidth = savedOptions.GetMinWidth(imageType);
- var downloaded = await DownloadImage(item, provider, result, list, minWidth, imageType, cancellationToken).ConfigureAwait(false);
+ var downloaded = await DownloadImage(item, libraryOptions, provider, result, list, minWidth, imageType, cancellationToken).ConfigureAwait(false);
if (downloaded)
{
@@ -305,7 +305,7 @@ namespace MediaBrowser.Providers.Manager
if (!item.LockedFields.Contains(MetadataFields.Backdrops))
{
minWidth = savedOptions.GetMinWidth(ImageType.Backdrop);
- await DownloadBackdrops(item, ImageType.Backdrop, backdropLimit, provider, result, list, minWidth, cancellationToken).ConfigureAwait(false);
+ await DownloadBackdrops(item, libraryOptions, ImageType.Backdrop, backdropLimit, provider, result, list, minWidth, cancellationToken).ConfigureAwait(false);
}
if (!item.LockedFields.Contains(MetadataFields.Screenshots))
@@ -314,7 +314,7 @@ namespace MediaBrowser.Providers.Manager
if (hasScreenshots != null)
{
minWidth = savedOptions.GetMinWidth(ImageType.Screenshot);
- await DownloadBackdrops(item, ImageType.Screenshot, screenshotLimit, provider, result, list, minWidth, cancellationToken).ConfigureAwait(false);
+ await DownloadBackdrops(item, libraryOptions, ImageType.Screenshot, screenshotLimit, provider, result, list, minWidth, cancellationToken).ConfigureAwait(false);
}
}
}
@@ -472,7 +472,7 @@ namespace MediaBrowser.Providers.Manager
return changed;
}
- private async Task<bool> DownloadImage(IHasImages item,
+ private async Task<bool> DownloadImage(IHasImages item, LibraryOptions libraryOptions,
IRemoteImageProvider provider,
RefreshResult result,
IEnumerable<RemoteImageInfo> images,
@@ -484,7 +484,7 @@ namespace MediaBrowser.Providers.Manager
.Where(i => i.Type == type && !(i.Width.HasValue && i.Width.Value < minWidth))
.ToList();
- if (EnableImageStub(item, type) && eligibleImages.Count > 0)
+ if (EnableImageStub(item, type, libraryOptions) && eligibleImages.Count > 0)
{
SaveImageStub(item, type, eligibleImages.Select(i => i.Url));
result.UpdateType = result.UpdateType | ItemUpdateType.ImageUpdate;
@@ -518,14 +518,14 @@ namespace MediaBrowser.Providers.Manager
return false;
}
- private bool EnableImageStub(IHasImages item, ImageType type)
+ private bool EnableImageStub(IHasImages item, ImageType type, LibraryOptions libraryOptions)
{
if (item is LiveTvProgram)
{
return true;
}
- if (_config.Configuration.DownloadImagesInAdvance)
+ if (libraryOptions.DownloadImagesInAdvance)
{
return false;
}
@@ -555,12 +555,6 @@ namespace MediaBrowser.Providers.Manager
return false;
case ImageType.Thumb:
return false;
- case ImageType.Logo:
- return false;
- case ImageType.Backdrop:
- return false;
- case ImageType.Screenshot:
- return false;
default:
return true;
}
@@ -585,7 +579,7 @@ namespace MediaBrowser.Providers.Manager
}, newIndex);
}
- private async Task DownloadBackdrops(IHasImages item, ImageType imageType, int limit, IRemoteImageProvider provider, RefreshResult result, IEnumerable<RemoteImageInfo> images, int minWidth, CancellationToken cancellationToken)
+ private async Task DownloadBackdrops(IHasImages item, LibraryOptions libraryOptions, ImageType imageType, int limit, IRemoteImageProvider provider, RefreshResult result, IEnumerable<RemoteImageInfo> images, int minWidth, CancellationToken cancellationToken)
{
foreach (var image in images.Where(i => i.Type == imageType))
{
@@ -601,7 +595,7 @@ namespace MediaBrowser.Providers.Manager
var url = image.Url;
- if (EnableImageStub(item, imageType))
+ if (EnableImageStub(item, imageType, libraryOptions))
{
SaveImageStub(item, imageType, new[] { url });
result.UpdateType = result.UpdateType | ItemUpdateType.ImageUpdate;
diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs
index a610df427..c470f55f2 100644
--- a/MediaBrowser.Providers/Manager/MetadataService.cs
+++ b/MediaBrowser.Providers/Manager/MetadataService.cs
@@ -11,6 +11,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
+using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Providers;
namespace MediaBrowser.Providers.Manager
@@ -120,6 +121,8 @@ namespace MediaBrowser.Providers.Manager
}
}
+ LibraryOptions libraryOptions = null;
+
// Next run remote image providers, but only if local image providers didn't throw an exception
if (!localImagesFailed && refreshOptions.ImageRefreshMode != ImageRefreshMode.ValidationOnly)
{
@@ -127,7 +130,12 @@ namespace MediaBrowser.Providers.Manager
if (providers.Count > 0)
{
- var result = await itemImageProvider.RefreshImages(itemOfType, providers, refreshOptions, config, cancellationToken).ConfigureAwait(false);
+ if (libraryOptions == null)
+ {
+ libraryOptions = LibraryManager.GetLibraryOptions((BaseItem)item);
+ }
+
+ var result = await itemImageProvider.RefreshImages(itemOfType, libraryOptions, providers, refreshOptions, config, cancellationToken).ConfigureAwait(false);
updateType = updateType | result.UpdateType;
if (result.Failures == 0)
@@ -180,8 +188,13 @@ namespace MediaBrowser.Providers.Manager
item.DateLastRefreshed = default(DateTime);
}
+ if (libraryOptions == null)
+ {
+ libraryOptions = LibraryManager.GetLibraryOptions((BaseItem)item);
+ }
+
// Save to database
- await SaveItem(metadataResult, updateType, cancellationToken).ConfigureAwait(false);
+ await SaveItem(metadataResult, libraryOptions, updateType, cancellationToken).ConfigureAwait(false);
}
await AfterMetadataRefresh(itemOfType, refreshOptions, cancellationToken).ConfigureAwait(false);
@@ -196,17 +209,19 @@ namespace MediaBrowser.Providers.Manager
lookupInfo.Year = result.ProductionYear;
}
- protected async Task SaveItem(MetadataResult<TItemType> result, ItemUpdateType reason, CancellationToken cancellationToken)
+ protected async Task SaveItem(MetadataResult<TItemType> result, LibraryOptions libraryOptions, ItemUpdateType reason, CancellationToken cancellationToken)
{
if (result.Item.SupportsPeople && result.People != null)
{
- await LibraryManager.UpdatePeople(result.Item as BaseItem, result.People.ToList());
- await SavePeopleMetadata(result.People, cancellationToken).ConfigureAwait(false);
+ var baseItem = result.Item as BaseItem;
+
+ await LibraryManager.UpdatePeople(baseItem, result.People.ToList());
+ await SavePeopleMetadata(result.People, libraryOptions, cancellationToken).ConfigureAwait(false);
}
await result.Item.UpdateToRepository(reason, cancellationToken).ConfigureAwait(false);
}
- private async Task SavePeopleMetadata(List<PersonInfo> people, CancellationToken cancellationToken)
+ private async Task SavePeopleMetadata(List<PersonInfo> people, LibraryOptions libraryOptions, CancellationToken cancellationToken)
{
foreach (var person in people)
{
@@ -229,7 +244,7 @@ namespace MediaBrowser.Providers.Manager
if (!string.IsNullOrWhiteSpace(person.ImageUrl) && !personEntity.HasImage(ImageType.Primary))
{
- await AddPersonImage(personEntity, person.ImageUrl, cancellationToken).ConfigureAwait(false);
+ await AddPersonImage(personEntity, libraryOptions, person.ImageUrl, cancellationToken).ConfigureAwait(false);
saveEntity = true;
updateType = updateType | ItemUpdateType.ImageUpdate;
@@ -243,9 +258,9 @@ namespace MediaBrowser.Providers.Manager
}
}
- private async Task AddPersonImage(Person personEntity, string imageUrl, CancellationToken cancellationToken)
+ private async Task AddPersonImage(Person personEntity, LibraryOptions libraryOptions, string imageUrl, CancellationToken cancellationToken)
{
- if (ServerConfigurationManager.Configuration.DownloadImagesInAdvance)
+ if (libraryOptions.DownloadImagesInAdvance)
{
try
{
@@ -301,6 +316,13 @@ namespace MediaBrowser.Providers.Manager
updateType |= ItemUpdateType.MetadataImport;
}
+ var inheritedTags = item.GetInheritedTags();
+ if (!inheritedTags.SequenceEqual(item.InheritedTags, StringComparer.Ordinal))
+ {
+ item.InheritedTags = inheritedTags;
+ updateType |= ItemUpdateType.MetadataImport;
+ }
+
return updateType;
}
@@ -517,7 +539,7 @@ namespace MediaBrowser.Providers.Manager
refreshResult.UpdateType = refreshResult.UpdateType | ItemUpdateType.MetadataImport;
// Only one local provider allowed per item
- if (IsFullLocalMetadata(localItem.Item))
+ if (item.IsLocked || IsFullLocalMetadata(localItem.Item))
{
hasLocalMetadata = true;
}
@@ -532,8 +554,6 @@ namespace MediaBrowser.Providers.Manager
}
catch (Exception ex)
{
- refreshResult.Failures++;
-
Logger.ErrorException("Error in {0}", ex, provider.Name);
// If a local provider fails, consider that a failure
@@ -631,6 +651,8 @@ namespace MediaBrowser.Providers.Manager
{
var refreshResult = new RefreshResult();
+ var results = new List<MetadataResult<TItemType>>();
+
foreach (var provider in providers)
{
var providerName = provider.GetType().Name;
@@ -647,7 +669,7 @@ namespace MediaBrowser.Providers.Manager
if (result.HasMetadata)
{
- MergeData(result, temp, new List<MetadataFields>(), false, false);
+ results.Add(result);
refreshResult.UpdateType = refreshResult.UpdateType | ItemUpdateType.MetadataDownload;
}
@@ -668,9 +690,49 @@ namespace MediaBrowser.Providers.Manager
}
}
+ var orderedResults = new List<MetadataResult<TItemType>>();
+ var preferredLanguage = NormalizeLanguage(id.MetadataLanguage);
+
+ // prioritize results with matching ResultLanguage
+ foreach (var result in results)
+ {
+ if (!result.QueriedById)
+ {
+ break;
+ }
+
+ if (string.Equals(NormalizeLanguage(result.ResultLanguage), preferredLanguage, StringComparison.OrdinalIgnoreCase) && result.QueriedById)
+ {
+ orderedResults.Add(result);
+ }
+ }
+
+ // add all other results
+ foreach (var result in results)
+ {
+ if (!orderedResults.Contains(result))
+ {
+ orderedResults.Add(result);
+ }
+ }
+
+ foreach (var result in results)
+ {
+ MergeData(result, temp, new List<MetadataFields>(), false, false);
+ }
+
return refreshResult;
}
+ private string NormalizeLanguage(string language)
+ {
+ if (string.IsNullOrWhiteSpace(language))
+ {
+ return "en";
+ }
+ return language;
+ }
+
private void MergeNewData(TItemType source, TIdType lookupInfo)
{
// Copy new provider id's that may have been obtained
diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs
index 25b9b4fd5..ae1d60eb9 100644
--- a/MediaBrowser.Providers/Manager/ProviderManager.cs
+++ b/MediaBrowser.Providers/Manager/ProviderManager.cs
@@ -20,6 +20,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
+using MediaBrowser.Common.IO;
using MediaBrowser.Model.Serialization;
namespace MediaBrowser.Providers.Manager
@@ -57,13 +58,13 @@ namespace MediaBrowser.Providers.Manager
private IMetadataService[] _metadataServices = { };
private IMetadataProvider[] _metadataProviders = { };
private IEnumerable<IMetadataSaver> _savers;
- private IImageSaver[] _imageSavers;
private readonly IServerApplicationPaths _appPaths;
private readonly IJsonSerializer _json;
private IExternalId[] _externalIds;
private readonly Func<ILibraryManager> _libraryManagerFactory;
+ private readonly IMemoryStreamProvider _memoryStreamProvider;
/// <summary>
/// Initializes a new instance of the <see cref="ProviderManager" /> class.
@@ -73,7 +74,7 @@ namespace MediaBrowser.Providers.Manager
/// <param name="libraryMonitor">The directory watchers.</param>
/// <param name="logManager">The log manager.</param>
/// <param name="fileSystem">The file system.</param>
- public ProviderManager(IHttpClient httpClient, IServerConfigurationManager configurationManager, ILibraryMonitor libraryMonitor, ILogManager logManager, IFileSystem fileSystem, IServerApplicationPaths appPaths, Func<ILibraryManager> libraryManagerFactory, IJsonSerializer json)
+ public ProviderManager(IHttpClient httpClient, IServerConfigurationManager configurationManager, ILibraryMonitor libraryMonitor, ILogManager logManager, IFileSystem fileSystem, IServerApplicationPaths appPaths, Func<ILibraryManager> libraryManagerFactory, IJsonSerializer json, IMemoryStreamProvider memoryStreamProvider)
{
_logger = logManager.GetLogger("ProviderManager");
_httpClient = httpClient;
@@ -83,26 +84,20 @@ namespace MediaBrowser.Providers.Manager
_appPaths = appPaths;
_libraryManagerFactory = libraryManagerFactory;
_json = json;
+ _memoryStreamProvider = memoryStreamProvider;
}
/// <summary>
/// Adds the metadata providers.
/// </summary>
- /// <param name="imageProviders">The image providers.</param>
- /// <param name="metadataServices">The metadata services.</param>
- /// <param name="metadataProviders">The metadata providers.</param>
- /// <param name="metadataSavers">The metadata savers.</param>
- /// <param name="imageSavers">The image savers.</param>
- /// <param name="externalIds">The external ids.</param>
public void AddParts(IEnumerable<IImageProvider> imageProviders, IEnumerable<IMetadataService> metadataServices,
IEnumerable<IMetadataProvider> metadataProviders, IEnumerable<IMetadataSaver> metadataSavers,
- IEnumerable<IImageSaver> imageSavers, IEnumerable<IExternalId> externalIds)
+ IEnumerable<IExternalId> externalIds)
{
ImageProviders = imageProviders.ToArray();
_metadataServices = metadataServices.OrderBy(i => i.Order).ToArray();
_metadataProviders = metadataProviders.ToArray();
- _imageSavers = imageSavers.ToArray();
_externalIds = externalIds.OrderBy(i => i.Name).ToArray();
_savers = metadataSavers.Where(i =>
@@ -142,12 +137,12 @@ namespace MediaBrowser.Providers.Manager
public Task SaveImage(IHasImages item, Stream source, string mimeType, ImageType type, int? imageIndex, CancellationToken cancellationToken)
{
- return new ImageSaver(ConfigurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, source, mimeType, type, imageIndex, cancellationToken);
+ return new ImageSaver(ConfigurationManager, _libraryMonitor, _fileSystem, _logger, _memoryStreamProvider).SaveImage(item, source, mimeType, type, imageIndex, cancellationToken);
}
public Task SaveImage(IHasImages item, Stream source, string mimeType, ImageType type, int? imageIndex, string internalCacheKey, CancellationToken cancellationToken)
{
- return new ImageSaver(ConfigurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, source, mimeType, type, imageIndex, internalCacheKey, cancellationToken);
+ return new ImageSaver(ConfigurationManager, _libraryMonitor, _fileSystem, _logger, _memoryStreamProvider).SaveImage(item, source, mimeType, type, imageIndex, internalCacheKey, cancellationToken);
}
public Task SaveImage(IHasImages item, string source, string mimeType, ImageType type, int? imageIndex, string internalCacheKey, CancellationToken cancellationToken)
@@ -159,7 +154,7 @@ namespace MediaBrowser.Providers.Manager
var fileStream = _fileSystem.GetFileStream(source, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true);
- return new ImageSaver(ConfigurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, fileStream, mimeType, type, imageIndex, internalCacheKey, cancellationToken);
+ return new ImageSaver(ConfigurationManager, _libraryMonitor, _fileSystem, _logger, _memoryStreamProvider).SaveImage(item, fileStream, mimeType, type, imageIndex, internalCacheKey, cancellationToken);
}
public async Task<IEnumerable<RemoteImageInfo>> GetAvailableRemoteImages(IHasImages item, RemoteImageQuery query, CancellationToken cancellationToken)
diff --git a/MediaBrowser.Providers/Manager/ProviderUtils.cs b/MediaBrowser.Providers/Manager/ProviderUtils.cs
index 5f23cf69c..fabe08f33 100644
--- a/MediaBrowser.Providers/Manager/ProviderUtils.cs
+++ b/MediaBrowser.Providers/Manager/ProviderUtils.cs
@@ -99,6 +99,11 @@ namespace MediaBrowser.Providers.Manager
target.CustomRating = source.CustomRating;
}
+ if (replaceData || string.IsNullOrEmpty(target.Tagline))
+ {
+ target.Tagline = source.Tagline;
+ }
+
if (!lockedFields.Contains(MetadataFields.Overview))
{
if (replaceData || string.IsNullOrEmpty(target.Overview))
@@ -167,15 +172,9 @@ namespace MediaBrowser.Providers.Manager
if (!lockedFields.Contains(MetadataFields.ProductionLocations))
{
- var sourceHasProductionLocations = source as IHasProductionLocations;
- var targetHasProductionLocations = target as IHasProductionLocations;
-
- if (sourceHasProductionLocations != null && targetHasProductionLocations != null)
+ if (replaceData || target.ProductionLocations.Count == 0)
{
- if (replaceData || targetHasProductionLocations.ProductionLocations.Count == 0)
- {
- targetHasProductionLocations.ProductionLocations = sourceHasProductionLocations.ProductionLocations;
- }
+ target.ProductionLocations = source.ProductionLocations;
}
}
@@ -200,7 +199,6 @@ namespace MediaBrowser.Providers.Manager
MergeMetascore(source, target, lockedFields, replaceData);
MergeCriticRating(source, target, lockedFields, replaceData);
MergeAwards(source, target, lockedFields, replaceData);
- MergeTaglines(source, target, lockedFields, replaceData);
MergeTrailers(source, target, lockedFields, replaceData);
MergeShortOverview(source, target, lockedFields, replaceData);
@@ -330,20 +328,6 @@ namespace MediaBrowser.Providers.Manager
}
}
- private static void MergeTaglines(BaseItem source, BaseItem target, List<MetadataFields> lockedFields, bool replaceData)
- {
- var sourceCast = source as IHasTaglines;
- var targetCast = target as IHasTaglines;
-
- if (sourceCast != null && targetCast != null)
- {
- if (replaceData || targetCast.Taglines.Count == 0)
- {
- targetCast.Taglines = sourceCast.Taglines;
- }
- }
- }
-
private static void MergeTrailers(BaseItem source, BaseItem target, List<MetadataFields> lockedFields, bool replaceData)
{
var sourceCast = source as IHasTrailers;
diff --git a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs
index 027341ee6..68fc80371 100644
--- a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs
+++ b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs
@@ -162,7 +162,7 @@ namespace MediaBrowser.Providers.MediaInfo
{
var audio = item as Audio;
- return item.LocationType == LocationType.FileSystem && audio != null && !audio.IsArchive;
+ return item.LocationType == LocationType.FileSystem && audio != null;
}
public bool HasChanged(IHasMetadata item, IDirectoryService directoryService)
diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs
index baa561487..afcf4b226 100644
--- a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs
+++ b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs
@@ -38,13 +38,6 @@ namespace MediaBrowser.Providers.MediaInfo
public async Task<ItemUpdateType> Probe<T>(T item, CancellationToken cancellationToken)
where T : Audio
{
- if (item.IsArchive)
- {
- var ext = Path.GetExtension(item.Path) ?? string.Empty;
- item.Container = ext.TrimStart('.');
- return ItemUpdateType.MetadataImport;
- }
-
var result = await GetMediaInfo(item, cancellationToken).ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested();
diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
index 0f8cf93fb..be0b2ca6d 100644
--- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
+++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
@@ -72,13 +72,6 @@ namespace MediaBrowser.Providers.MediaInfo
CancellationToken cancellationToken)
where T : Video
{
- if (item.IsArchive)
- {
- var ext = Path.GetExtension(item.Path) ?? string.Empty;
- item.Container = ext.TrimStart('.');
- return ItemUpdateType.MetadataImport;
- }
-
var isoMount = await MountIsoIfNeeded(item, cancellationToken).ConfigureAwait(false);
BlurayDiscInfo blurayDiscInfo = null;
@@ -238,22 +231,6 @@ namespace MediaBrowser.Providers.MediaInfo
if (options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh ||
options.MetadataRefreshMode == MetadataRefreshMode.Default)
{
- var chapterOptions = _chapterManager.GetConfiguration();
-
- try
- {
- var remoteChapters = await DownloadChapters(video, chapters, chapterOptions, cancellationToken).ConfigureAwait(false);
-
- if (remoteChapters.Count > 0)
- {
- chapters = remoteChapters;
- }
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error downloading chapters", ex);
- }
-
if (chapters.Count == 0 && mediaStreams.Any(i => i.Type == MediaStreamType.Video))
{
AddDummyChapters(video, chapters);
@@ -262,8 +239,8 @@ namespace MediaBrowser.Providers.MediaInfo
NormalizeChapterNames(chapters);
var libraryOptions = _libraryManager.GetLibraryOptions(video);
- var extractDuringScan = chapterOptions.ExtractDuringLibraryScan;
- if (libraryOptions != null && libraryOptions.SchemaVersion >= 2)
+ var extractDuringScan = false;
+ if (libraryOptions != null)
{
extractDuringScan = libraryOptions.ExtractChapterImagesDuringLibraryScan;
}
@@ -561,52 +538,6 @@ namespace MediaBrowser.Providers.MediaInfo
currentStreams.AddRange(externalSubtitleStreams);
}
- private async Task<List<ChapterInfo>> DownloadChapters(Video video, List<ChapterInfo> currentChapters, ChapterOptions options, CancellationToken cancellationToken)
- {
- if ((options.DownloadEpisodeChapters &&
- video is Episode) ||
- (options.DownloadMovieChapters &&
- video is Movie))
- {
- var results = await _chapterManager.Search(video, cancellationToken).ConfigureAwait(false);
-
- var result = results.FirstOrDefault();
-
- if (result != null)
- {
- var chapters = await _chapterManager.GetChapters(result.Id, cancellationToken).ConfigureAwait(false);
-
- var chapterInfos = chapters.Chapters.Select(i => new ChapterInfo
- {
- Name = i.Name,
- StartPositionTicks = i.StartPositionTicks
-
- }).ToList();
-
- if (chapterInfos.All(i => i.StartPositionTicks == 0))
- {
- if (currentChapters.Count >= chapterInfos.Count)
- {
- var index = 0;
- foreach (var info in chapterInfos)
- {
- info.StartPositionTicks = currentChapters[index].StartPositionTicks;
- index++;
- }
- }
- else
- {
- chapterInfos.Clear();
- }
- }
-
- return chapterInfos;
- }
- }
-
- return new List<ChapterInfo>();
- }
-
/// <summary>
/// The dummy chapter duration
/// </summary>
diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs
index f64b7b792..024171f40 100644
--- a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs
+++ b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs
@@ -117,7 +117,7 @@ namespace MediaBrowser.Providers.MediaInfo
{
get
{
- return new[] { ".srt", ".ssa", ".ass", ".sub" };
+ return new[] { ".srt", ".ssa", ".ass", ".sub", ".smi", ".sami" };
}
}
diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs
index 79da291b7..2490f7145 100644
--- a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs
+++ b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs
@@ -14,6 +14,8 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using System.IO;
+using MediaBrowser.Model.Serialization;
namespace MediaBrowser.Providers.MediaInfo
{
@@ -24,14 +26,16 @@ namespace MediaBrowser.Providers.MediaInfo
private readonly ISubtitleManager _subtitleManager;
private readonly IMediaSourceManager _mediaSourceManager;
private readonly ILogger _logger;
+ private IJsonSerializer _json;
- public SubtitleScheduledTask(ILibraryManager libraryManager, IServerConfigurationManager config, ISubtitleManager subtitleManager, ILogger logger, IMediaSourceManager mediaSourceManager)
+ public SubtitleScheduledTask(ILibraryManager libraryManager, IJsonSerializer json, IServerConfigurationManager config, ISubtitleManager subtitleManager, ILogger logger, IMediaSourceManager mediaSourceManager)
{
_libraryManager = libraryManager;
_config = config;
_subtitleManager = subtitleManager;
_logger = logger;
_mediaSourceManager = mediaSourceManager;
+ _json = json;
}
public string Name
@@ -58,38 +62,65 @@ namespace MediaBrowser.Providers.MediaInfo
{
var options = GetOptions();
- var videos = _libraryManager.RootFolder
- .GetRecursiveChildren(i =>
- {
- if (!(i is Video))
- {
- return false;
- }
+ var types = new List<string>();
- if (i.LocationType == LocationType.Remote || i.LocationType == LocationType.Virtual)
- {
- return false;
- }
+ if (options.DownloadEpisodeSubtitles)
+ {
+ types.Add("Episode");
+ }
+ if (options.DownloadMovieSubtitles)
+ {
+ types.Add("Movie");
+ }
- return (options.DownloadEpisodeSubtitles &&
- i is Episode) ||
- (options.DownloadMovieSubtitles &&
- i is Movie);
- })
- .Cast<Video>()
+ if (types.Count == 0)
+ {
+ return;
+ }
+
+ var videos = _libraryManager.GetItemList(new InternalItemsQuery
+ {
+ MediaTypes = new string[] { MediaType.Video },
+ IsVirtualItem = false,
+ ExcludeLocationTypes = new LocationType[] { LocationType.Remote, LocationType.Virtual },
+ IncludeItemTypes = types.ToArray()
+
+ }).OfType<Video>()
.ToList();
+ var failHistoryPath = Path.Combine(_config.ApplicationPaths.CachePath, "subtitlehistory.json");
+ var history = GetHistory(failHistoryPath);
+
var numComplete = 0;
foreach (var video in videos)
{
+ DateTime lastAttempt;
+ if (history.TryGetValue(video.Id.ToString("N"), out lastAttempt))
+ {
+ if ((DateTime.UtcNow - lastAttempt).TotalDays <= 7)
+ {
+ continue;
+ }
+ }
+
try
{
- await DownloadSubtitles(video, options, cancellationToken).ConfigureAwait(false);
+ var shouldRetry = await DownloadSubtitles(video, options, cancellationToken).ConfigureAwait(false);
+
+ if (shouldRetry)
+ {
+ history[video.Id.ToString("N")] = DateTime.UtcNow;
+ }
+ else
+ {
+ history.Remove(video.Id.ToString("N"));
+ }
}
catch (Exception ex)
{
_logger.ErrorException("Error downloading subtitles for {0}", ex, video.Path);
+ history[video.Id.ToString("N")] = DateTime.UtcNow;
}
// Update progress
@@ -99,9 +130,23 @@ namespace MediaBrowser.Providers.MediaInfo
progress.Report(100 * percent);
}
+
+ _json.SerializeToFile(history, failHistoryPath);
}
- private async Task DownloadSubtitles(Video video, SubtitleOptions options, CancellationToken cancellationToken)
+ private Dictionary<string,DateTime> GetHistory(string path)
+ {
+ try
+ {
+ return _json.DeserializeFromFile<Dictionary<string, DateTime>>(path);
+ }
+ catch
+ {
+ return new Dictionary<string, DateTime>();
+ }
+ }
+
+ private async Task<bool> DownloadSubtitles(Video video, SubtitleOptions options, CancellationToken cancellationToken)
{
if ((options.DownloadEpisodeSubtitles &&
video is Episode) ||
@@ -124,8 +169,13 @@ namespace MediaBrowser.Providers.MediaInfo
if (downloadedLanguages.Count > 0)
{
await video.RefreshMetadata(cancellationToken).ConfigureAwait(false);
+ return false;
}
+
+ return true;
}
+
+ return false;
}
public IEnumerable<ITaskTrigger> GetDefaultTriggers()
diff --git a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs
index 2ad02da2e..45d1c3d80 100644
--- a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs
+++ b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs
@@ -134,7 +134,7 @@ namespace MediaBrowser.Providers.MediaInfo
}
}
- extractedImagePath = await _mediaEncoder.ExtractVideoImage(inputPath, protocol, videoIndex, cancellationToken).ConfigureAwait(false);
+ extractedImagePath = await _mediaEncoder.ExtractVideoImage(inputPath, item.Container, protocol, videoIndex, cancellationToken).ConfigureAwait(false);
}
else
{
@@ -145,7 +145,7 @@ namespace MediaBrowser.Providers.MediaInfo
? TimeSpan.FromTicks(Convert.ToInt64(item.RunTimeTicks.Value * .1))
: TimeSpan.FromSeconds(10);
- extractedImagePath = await _mediaEncoder.ExtractVideoImage(inputPath, protocol, item.Video3DFormat, imageOffset, cancellationToken).ConfigureAwait(false);
+ extractedImagePath = await _mediaEncoder.ExtractVideoImage(inputPath, item.Container, protocol, item.Video3DFormat, imageOffset, cancellationToken).ConfigureAwait(false);
}
return new DynamicImageResponse
@@ -174,8 +174,7 @@ namespace MediaBrowser.Providers.MediaInfo
{
var video = item as Video;
- if (item.LocationType == LocationType.FileSystem && video != null && !video.IsPlaceHolder &&
- !video.IsShortcut && !video.IsArchive)
+ if (item.LocationType == LocationType.FileSystem && video != null && !video.IsPlaceHolder && !video.IsShortcut)
{
return true;
}
diff --git a/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs b/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs
index 18f177932..2a40e4d85 100644
--- a/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs
+++ b/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs
@@ -20,6 +20,7 @@ using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
+using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Providers.TV;
namespace MediaBrowser.Providers.Movies
@@ -59,30 +60,13 @@ namespace MediaBrowser.Providers.Movies
public bool Supports(IHasImages item)
{
- //var channelItem = item as IChannelMediaItem;
-
- //if (channelItem != null)
- //{
- // if (channelItem.ContentType == ChannelMediaContentType.Movie)
- // {
- // return true;
- // }
- // if (channelItem.ContentType == ChannelMediaContentType.MovieExtra)
- // {
- // if (channelItem.ExtraType == ExtraType.Trailer)
- // {
- // return true;
- // }
- // }
- //}
-
// Supports images for tv movies
- //var tvProgram = item as LiveTvProgram;
- //if (tvProgram != null && tvProgram.IsMovie)
- //{
- // return true;
- //}
-
+ var tvProgram = item as LiveTvProgram;
+ if (tvProgram != null && tvProgram.IsMovie)
+ {
+ return true;
+ }
+
return item is Movie || item is BoxSet || item is MusicVideo;
}
diff --git a/MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs b/MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs
index 1bf4ed6c0..82a2dfbe9 100644
--- a/MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs
+++ b/MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs
@@ -14,6 +14,7 @@ using System.Net;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
+using MediaBrowser.Model.Extensions;
namespace MediaBrowser.Providers.Movies
{
@@ -125,11 +126,7 @@ namespace MediaBrowser.Providers.Movies
movie.Name = movieData.GetTitle() ?? movie.Name;
- var hasOriginalTitle = movie as IHasOriginalTitle;
- if (hasOriginalTitle != null)
- {
- hasOriginalTitle.OriginalTitle = movieData.GetOriginalTitle();
- }
+ movie.OriginalTitle = movieData.GetOriginalTitle();
// Bug in Mono: WebUtility.HtmlDecode should return null if the string is null but in Mono it generate an System.ArgumentNullException.
movie.Overview = movieData.overview != null ? WebUtility.HtmlDecode(movieData.overview) : null;
@@ -146,24 +143,15 @@ namespace MediaBrowser.Providers.Movies
if (!string.IsNullOrEmpty(movieData.tagline))
{
- var hasTagline = movie as IHasTaglines;
- if (hasTagline != null)
- {
- hasTagline.Taglines.Clear();
- hasTagline.AddTagline(movieData.tagline);
- }
+ movie.Tagline = movieData.tagline;
}
if (movieData.production_countries != null)
{
- var hasProductionLocations = movie as IHasProductionLocations;
- if (hasProductionLocations != null)
- {
- hasProductionLocations.ProductionLocations = movieData
- .production_countries
- .Select(i => i.name)
- .ToList();
- }
+ movie.ProductionLocations = movieData
+ .production_countries
+ .Select(i => i.name)
+ .ToList();
}
movie.SetProviderId(MetadataProviders.Tmdb, movieData.id.ToString(_usCulture));
@@ -197,13 +185,17 @@ namespace MediaBrowser.Providers.Movies
{
var releases = movieData.releases.countries.Where(i => !string.IsNullOrWhiteSpace(i.certification)).ToList();
- var ourRelease = releases.FirstOrDefault(c => c.iso_3166_1.Equals(preferredCountryCode, StringComparison.OrdinalIgnoreCase));
- var usRelease = releases.FirstOrDefault(c => c.iso_3166_1.Equals("US", StringComparison.OrdinalIgnoreCase));
+ var ourRelease = releases.FirstOrDefault(c => string.Equals(c.iso_3166_1, preferredCountryCode, StringComparison.OrdinalIgnoreCase));
+ var usRelease = releases.FirstOrDefault(c => string.Equals(c.iso_3166_1, "US", StringComparison.OrdinalIgnoreCase));
if (ourRelease != null)
{
var ratingPrefix = string.Equals(preferredCountryCode, "us", StringComparison.OrdinalIgnoreCase) ? "" : preferredCountryCode + "-";
- movie.OfficialRating = ratingPrefix + ourRelease.certification;
+ var newRating = ratingPrefix + ourRelease.certification;
+
+ newRating = newRating.Replace("de-", "FSK-", StringComparison.OrdinalIgnoreCase);
+
+ movie.OfficialRating = newRating;
}
else if (usRelease != null)
{
diff --git a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs
index a0ce80610..9ed8f0a00 100644
--- a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs
+++ b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs
@@ -85,7 +85,8 @@ namespace MediaBrowser.Providers.Music
{
var result = new RemoteSearchResult
{
- Name = i.Title
+ Name = i.Title,
+ ProductionYear = i.Year
};
if (!string.IsNullOrWhiteSpace(i.ReleaseId))
@@ -94,7 +95,7 @@ namespace MediaBrowser.Providers.Music
}
if (!string.IsNullOrWhiteSpace(i.ReleaseGroupId))
{
- result.SetProviderId(MetadataProviders.MusicBrainzAlbum, i.ReleaseGroupId);
+ result.SetProviderId(MetadataProviders.MusicBrainzReleaseGroup, i.ReleaseGroupId);
}
return result;
@@ -117,16 +118,22 @@ namespace MediaBrowser.Providers.Music
var releaseResult = await GetReleaseResult(artistMusicBrainzId, id.GetAlbumArtist(), id.Name, cancellationToken).ConfigureAwait(false);
- if (!string.IsNullOrEmpty(releaseResult.ReleaseId))
+ if (releaseResult != null)
{
- releaseId = releaseResult.ReleaseId;
- result.HasMetadata = true;
- }
+ if (!string.IsNullOrEmpty(releaseResult.ReleaseId))
+ {
+ releaseId = releaseResult.ReleaseId;
+ result.HasMetadata = true;
+ }
- if (!string.IsNullOrEmpty(releaseResult.ReleaseGroupId))
- {
- releaseGroupId = releaseResult.ReleaseGroupId;
- result.HasMetadata = true;
+ if (!string.IsNullOrEmpty(releaseResult.ReleaseGroupId))
+ {
+ releaseGroupId = releaseResult.ReleaseGroupId;
+ result.HasMetadata = true;
+ }
+
+ result.Item.ProductionYear = releaseResult.Year;
+ result.Item.Overview = releaseResult.Overview;
}
}
@@ -205,6 +212,8 @@ namespace MediaBrowser.Providers.Music
public string ReleaseId;
public string ReleaseGroupId;
public string Title;
+ public string Overview;
+ public int? Year;
public static List<ReleaseResult> Parse(XmlDocument doc, int? limit = null)
{
@@ -237,7 +246,9 @@ namespace MediaBrowser.Providers.Music
{
ReleaseId = releaseId,
ReleaseGroupId = releaseGroupId,
- Title = GetTitleFromReleaseNode(node)
+ Title = GetValueFromReleaseNode(node, "title"),
+ Overview = GetValueFromReleaseNode(node, "annotation"),
+ Year = GetYearFromReleaseNode(node, "date")
});
if (limit.HasValue && list.Count >= limit.Value)
@@ -251,14 +262,37 @@ namespace MediaBrowser.Providers.Music
return list;
}
- private static string GetTitleFromReleaseNode(XmlNode node)
+ private static int? GetYearFromReleaseNode(XmlNode node, string name)
+ {
+ var subNodes = node.ChildNodes;
+ if (subNodes != null)
+ {
+ foreach (var subNode in subNodes.Cast<XmlNode>())
+ {
+ if (string.Equals(subNode.Name, name, StringComparison.OrdinalIgnoreCase))
+ {
+ DateTime date;
+ if (DateTime.TryParse(subNode.InnerText, out date))
+ {
+ return date.Year;
+ }
+
+ return null;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private static string GetValueFromReleaseNode(XmlNode node, string name)
{
var subNodes = node.ChildNodes;
if (subNodes != null)
{
foreach (var subNode in subNodes.Cast<XmlNode>())
{
- if (string.Equals(subNode.Name, "title", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(subNode.Name, name, StringComparison.OrdinalIgnoreCase))
{
return subNode.InnerText;
}
diff --git a/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs b/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs
index 88635bf06..88128bdc7 100644
--- a/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs
+++ b/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs
@@ -86,7 +86,7 @@ namespace MediaBrowser.Providers.Music
if (node.Attributes != null)
{
string name = null;
-
+ string overview = null;
string mbzId = node.Attributes["id"].Value;
foreach (var child in node.ChildNodes.Cast<XmlNode>())
@@ -94,7 +94,10 @@ namespace MediaBrowser.Providers.Music
if (string.Equals(child.Name, "name", StringComparison.OrdinalIgnoreCase))
{
name = child.InnerText;
- break;
+ }
+ if (string.Equals(child.Name, "annotation", StringComparison.OrdinalIgnoreCase))
+ {
+ overview = child.InnerText;
}
}
@@ -102,7 +105,8 @@ namespace MediaBrowser.Providers.Music
{
var result = new RemoteSearchResult
{
- Name = name
+ Name = name,
+ Overview = overview
};
result.SetProviderId(MetadataProviders.MusicBrainzArtist, mbzId);
@@ -135,6 +139,7 @@ namespace MediaBrowser.Providers.Music
{
musicBrainzId = singleResult.GetProviderId(MetadataProviders.MusicBrainzArtist);
//result.Item.Name = singleResult.Name;
+ result.Item.Overview = singleResult.Overview;
}
}
diff --git a/MediaBrowser.Providers/Omdb/OmdbItemProvider.cs b/MediaBrowser.Providers/Omdb/OmdbItemProvider.cs
index 914b775af..428bde2f2 100644
--- a/MediaBrowser.Providers/Omdb/OmdbItemProvider.cs
+++ b/MediaBrowser.Providers/Omdb/OmdbItemProvider.cs
@@ -212,13 +212,15 @@ namespace MediaBrowser.Providers.Omdb
{
var result = new MetadataResult<Series>
{
- Item = new Series()
+ Item = new Series(),
+ QueriedById = true
};
var imdbId = info.GetProviderId(MetadataProviders.Imdb);
if (string.IsNullOrWhiteSpace(imdbId))
{
imdbId = await GetSeriesImdbId(info, cancellationToken).ConfigureAwait(false);
+ result.QueriedById = false;
}
if (!string.IsNullOrEmpty(imdbId))
@@ -251,13 +253,15 @@ namespace MediaBrowser.Providers.Omdb
{
var result = new MetadataResult<T>
{
- Item = new T()
+ Item = new T(),
+ QueriedById = true
};
var imdbId = info.GetProviderId(MetadataProviders.Imdb);
if (string.IsNullOrWhiteSpace(imdbId))
{
imdbId = await GetMovieImdbId(info, cancellationToken).ConfigureAwait(false);
+ result.QueriedById = false;
}
if (!string.IsNullOrEmpty(imdbId))
diff --git a/MediaBrowser.Providers/People/MovieDbPersonProvider.cs b/MediaBrowser.Providers/People/MovieDbPersonProvider.cs
index bb17b83ec..c954e6323 100644
--- a/MediaBrowser.Providers/People/MovieDbPersonProvider.cs
+++ b/MediaBrowser.Providers/People/MovieDbPersonProvider.cs
@@ -179,7 +179,11 @@ namespace MediaBrowser.Providers.People
item.Name = info.name;
item.HomePageUrl = info.homepage;
- item.PlaceOfBirth = info.place_of_birth;
+
+ if (!string.IsNullOrWhiteSpace(info.place_of_birth))
+ {
+ item.ProductionLocations = new List<string> { info.place_of_birth };
+ }
item.Overview = info.biography;
DateTime date;
diff --git a/MediaBrowser.Providers/People/PersonMetadataService.cs b/MediaBrowser.Providers/People/PersonMetadataService.cs
index 0be5773db..0f8bd8b66 100644
--- a/MediaBrowser.Providers/People/PersonMetadataService.cs
+++ b/MediaBrowser.Providers/People/PersonMetadataService.cs
@@ -15,14 +15,6 @@ namespace MediaBrowser.Providers.People
protected override void MergeData(MetadataResult<Person> source, MetadataResult<Person> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
{
ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
-
- var sourceItem = source.Item;
- var targetItem = target.Item;
-
- if (replaceData || string.IsNullOrEmpty(targetItem.PlaceOfBirth))
- {
- targetItem.PlaceOfBirth = sourceItem.PlaceOfBirth;
- }
}
public PersonMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager)
diff --git a/MediaBrowser.Providers/Studios/StudiosImageProvider.cs b/MediaBrowser.Providers/Studios/StudiosImageProvider.cs
index 62109243d..bfb7eb8fd 100644
--- a/MediaBrowser.Providers/Studios/StudiosImageProvider.cs
+++ b/MediaBrowser.Providers/Studios/StudiosImageProvider.cs
@@ -37,7 +37,7 @@ namespace MediaBrowser.Providers.Studios
public static string ProviderName
{
- get { return "Media Browser Designs"; }
+ get { return "Emby Designs"; }
}
public bool Supports(IHasImages item)
@@ -137,7 +137,7 @@ namespace MediaBrowser.Providers.Studios
{
CancellationToken = cancellationToken,
Url = url,
- ResourcePool = GenreImageProvider.ImageDownloadResourcePool
+ BufferContent = false
});
}
}
diff --git a/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs b/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs
index a12402f4f..9c212e8a0 100644
--- a/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs
+++ b/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs
@@ -118,7 +118,7 @@ namespace MediaBrowser.Providers.TV
var hasNewEpisodes = false;
- if (_config.Configuration.EnableInternetProviders && addNewItems)
+ if (addNewItems && !group.Any(i => !i.IsInternetMetadataEnabled()))
{
var seriesConfig = _config.Configuration.MetadataOptions.FirstOrDefault(i => string.Equals(i.ItemType, typeof(Series).Name, StringComparison.OrdinalIgnoreCase));
diff --git a/MediaBrowser.Providers/TV/Omdb/OmdbEpisodeProvider.cs b/MediaBrowser.Providers/TV/Omdb/OmdbEpisodeProvider.cs
index 621f66514..78bce241f 100644
--- a/MediaBrowser.Providers/TV/Omdb/OmdbEpisodeProvider.cs
+++ b/MediaBrowser.Providers/TV/Omdb/OmdbEpisodeProvider.cs
@@ -43,7 +43,8 @@ namespace MediaBrowser.Providers.TV
{
var result = new MetadataResult<Episode>()
{
- Item = new Episode()
+ Item = new Episode(),
+ QueriedById = true
};
// Allowing this will dramatically increase scan times
diff --git a/MediaBrowser.Providers/TV/SeriesPostScanTask.cs b/MediaBrowser.Providers/TV/SeriesPostScanTask.cs
index d6ae84b91..e038a3d28 100644
--- a/MediaBrowser.Providers/TV/SeriesPostScanTask.cs
+++ b/MediaBrowser.Providers/TV/SeriesPostScanTask.cs
@@ -47,7 +47,7 @@ namespace MediaBrowser.Providers.TV
return RunInternal(progress, cancellationToken);
}
- private async Task RunInternal(IProgress<double> progress, CancellationToken cancellationToken)
+ private Task RunInternal(IProgress<double> progress, CancellationToken cancellationToken)
{
var seriesList = _libraryManager.GetItemList(new InternalItemsQuery()
{
@@ -59,34 +59,7 @@ namespace MediaBrowser.Providers.TV
var seriesGroups = FindSeriesGroups(seriesList).Where(g => !string.IsNullOrEmpty(g.Key)).ToList();
- await new MissingEpisodeProvider(_logger, _config, _libraryManager, _localization, _fileSystem)
- .Run(seriesGroups, true, cancellationToken).ConfigureAwait(false);
-
- var numComplete = 0;
-
- foreach (var series in seriesList)
- {
- cancellationToken.ThrowIfCancellationRequested();
-
- var episodes = series.GetRecursiveChildren(i => i is Episode)
- .Cast<Episode>()
- .ToList();
-
- var physicalEpisodes = episodes.Where(i => i.LocationType != LocationType.Virtual)
- .ToList();
-
- series.SpecialFeatureIds = physicalEpisodes
- .Where(i => i.ParentIndexNumber.HasValue && i.ParentIndexNumber.Value == 0)
- .Select(i => i.Id)
- .ToList();
-
- numComplete++;
- double percent = numComplete;
- percent /= seriesList.Count;
- percent *= 100;
-
- progress.Report(percent);
- }
+ return new MissingEpisodeProvider(_logger, _config, _libraryManager, _localization, _fileSystem).Run(seriesGroups, true, cancellationToken);
}
internal static IEnumerable<IGrouping<string, Series>> FindSeriesGroups(List<Series> seriesList)
diff --git a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeProvider.cs b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeProvider.cs
index bc9842b73..748124c03 100644
--- a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeProvider.cs
+++ b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeProvider.cs
@@ -91,6 +91,13 @@ namespace MediaBrowser.Providers.TV
var response = await GetEpisodeInfo(seriesTmdbId, seasonNumber.Value, episodeNumber.Value, info.MetadataLanguage, cancellationToken).ConfigureAwait(false);
result.HasMetadata = true;
+ result.QueriedById = true;
+
+ if (!string.IsNullOrEmpty(response.overview))
+ {
+ // if overview is non-empty, we can assume that localized data was returned
+ result.ResultLanguage = info.MetadataLanguage;
+ }
var item = new Episode();
result.Item = item;
diff --git a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeriesProvider.cs b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeriesProvider.cs
index 3245a2c85..fb0678029 100644
--- a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeriesProvider.cs
+++ b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeriesProvider.cs
@@ -119,6 +119,7 @@ namespace MediaBrowser.Providers.TV
public async Task<MetadataResult<Series>> GetMetadata(SeriesInfo info, CancellationToken cancellationToken)
{
var result = new MetadataResult<Series>();
+ result.QueriedById = true;
var tmdbId = info.GetProviderId(MetadataProviders.Tmdb);
@@ -154,6 +155,7 @@ namespace MediaBrowser.Providers.TV
if (string.IsNullOrEmpty(tmdbId))
{
+ result.QueriedById = false;
var searchResults = await new MovieDbSearch(_logger, _jsonSerializer, _libraryManager).GetSearchResults(info, cancellationToken).ConfigureAwait(false);
var searchResult = searchResults.FirstOrDefault();
@@ -168,7 +170,7 @@ namespace MediaBrowser.Providers.TV
{
cancellationToken.ThrowIfCancellationRequested();
- result.Item = await FetchSeriesData(tmdbId, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false);
+ result = await FetchMovieData(tmdbId, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false);
result.HasMetadata = result.Item != null;
}
@@ -176,7 +178,7 @@ namespace MediaBrowser.Providers.TV
return result;
}
- private async Task<Series> FetchSeriesData(string tmdbId, string language, string preferredCountryCode, CancellationToken cancellationToken)
+ private async Task<MetadataResult<Series>> FetchMovieData(string tmdbId, string language, string preferredCountryCode, CancellationToken cancellationToken)
{
string dataFilePath = null;
RootObject seriesInfo = null;
@@ -194,16 +196,18 @@ namespace MediaBrowser.Providers.TV
tmdbId = seriesInfo.id.ToString(_usCulture);
dataFilePath = GetDataFilePath(tmdbId, language);
- _fileSystem.CreateDirectory(Path.GetDirectoryName(dataFilePath));
+ _fileSystem.CreateDirectory(Path.GetDirectoryName(dataFilePath));
_jsonSerializer.SerializeToFile(seriesInfo, dataFilePath);
await EnsureSeriesInfo(tmdbId, language, cancellationToken).ConfigureAwait(false);
- var item = new Series();
+ var result = new MetadataResult<Series>();
+ result.Item = new Series();
+ result.ResultLanguage = seriesInfo.ResultLanguage;
- ProcessMainInfo(item, seriesInfo, preferredCountryCode);
+ ProcessMainInfo(result.Item, seriesInfo, preferredCountryCode);
- return item;
+ return result;
}
private void ProcessMainInfo(Series series, RootObject seriesInfo, string preferredCountryCode)
@@ -324,7 +328,7 @@ namespace MediaBrowser.Providers.TV
var dataFilePath = GetDataFilePath(id, preferredMetadataLanguage);
- _fileSystem.CreateDirectory(Path.GetDirectoryName(dataFilePath));
+ _fileSystem.CreateDirectory(Path.GetDirectoryName(dataFilePath));
_jsonSerializer.SerializeToFile(mainResult, dataFilePath);
}
@@ -354,6 +358,11 @@ namespace MediaBrowser.Providers.TV
}).ConfigureAwait(false))
{
mainResult = _jsonSerializer.DeserializeFromStream<RootObject>(json);
+
+ if (!string.IsNullOrEmpty(language))
+ {
+ mainResult.ResultLanguage = language;
+ }
}
cancellationToken.ThrowIfCancellationRequested();
@@ -385,6 +394,7 @@ namespace MediaBrowser.Providers.TV
var englishResult = _jsonSerializer.DeserializeFromStream<RootObject>(json);
mainResult.overview = englishResult.overview;
+ mainResult.ResultLanguage = "en";
}
}
@@ -627,6 +637,7 @@ namespace MediaBrowser.Providers.TV
public ExternalIds external_ids { get; set; }
public Videos videos { get; set; }
public ContentRatings content_ratings { get; set; }
+ public string ResultLanguage { get; set; }
}
public int Order
diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs
index a41a95c12..41a2282d8 100644
--- a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs
+++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs
@@ -108,6 +108,7 @@ namespace MediaBrowser.Providers.TV
public async Task<MetadataResult<Episode>> GetMetadata(EpisodeInfo searchInfo, CancellationToken cancellationToken)
{
var result = new MetadataResult<Episode>();
+ result.QueriedById = true;
if (TvdbSeriesProvider.IsValidSeries(searchInfo.SeriesProviderIds) &&
(searchInfo.IndexNumber.HasValue || searchInfo.PremiereDate.HasValue))
@@ -715,6 +716,17 @@ namespace MediaBrowser.Providers.TV
break;
}
+ case "Language":
+ {
+ var val = reader.ReadElementContentAsString();
+
+ if (!string.IsNullOrWhiteSpace(val))
+ {
+ result.ResultLanguage = val;
+ }
+
+ break;
+ }
default:
reader.Skip();
diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs
index 215e0640f..d9e1037d8 100644
--- a/MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs
+++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs
@@ -74,12 +74,6 @@ namespace MediaBrowser.Providers.TV
/// <returns>Task.</returns>
public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
{
- if (!_config.Configuration.EnableInternetProviders)
- {
- progress.Report(100);
- return;
- }
-
var seriesConfig = _config.Configuration.MetadataOptions.FirstOrDefault(i => string.Equals(i.ItemType, typeof(Series).Name, StringComparison.OrdinalIgnoreCase));
if (seriesConfig != null && seriesConfig.DisabledMetadataFetchers.Contains(TvdbSeriesProvider.Current.Name, StringComparer.OrdinalIgnoreCase))
@@ -116,7 +110,9 @@ namespace MediaBrowser.Providers.TV
IncludeItemTypes = new[] { typeof(Series).Name },
Recursive = true,
GroupByPresentationUniqueKey = false
- }).Cast<Series>();
+
+ }).Cast<Series>()
+ .ToList();
var seriesIdsInLibrary = seriesList
.Where(i => !string.IsNullOrEmpty(i.GetProviderId(MetadataProviders.Tvdb)))
@@ -126,6 +122,13 @@ namespace MediaBrowser.Providers.TV
var missingSeries = seriesIdsInLibrary.Except(existingDirectories, StringComparer.OrdinalIgnoreCase)
.ToList();
+ var enableInternetProviders = seriesList.Count == 0 ? false : seriesList[0].IsInternetMetadataEnabled();
+ if (!enableInternetProviders)
+ {
+ progress.Report(100);
+ return;
+ }
+
// If this is our first time, update all series
if (string.IsNullOrEmpty(lastUpdateTime))
{
diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs
index 66a02eba2..2572a4f58 100644
--- a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs
+++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs
@@ -21,6 +21,7 @@ using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using CommonIO;
+using MediaBrowser.Common.IO;
namespace MediaBrowser.Providers.TV
{
@@ -38,8 +39,9 @@ namespace MediaBrowser.Providers.TV
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private readonly ILogger _logger;
private readonly ILibraryManager _libraryManager;
+ private readonly IMemoryStreamProvider _memoryStreamProvider;
- public TvdbSeriesProvider(IZipClient zipClient, IHttpClient httpClient, IFileSystem fileSystem, IServerConfigurationManager config, ILogger logger, ILibraryManager libraryManager)
+ public TvdbSeriesProvider(IZipClient zipClient, IHttpClient httpClient, IFileSystem fileSystem, IServerConfigurationManager config, ILogger logger, ILibraryManager libraryManager, IMemoryStreamProvider memoryStreamProvider)
{
_zipClient = zipClient;
_httpClient = httpClient;
@@ -47,6 +49,7 @@ namespace MediaBrowser.Providers.TV
_config = config;
_logger = logger;
_libraryManager = libraryManager;
+ _memoryStreamProvider = memoryStreamProvider;
Current = this;
}
@@ -93,9 +96,11 @@ namespace MediaBrowser.Providers.TV
public async Task<MetadataResult<Series>> GetMetadata(SeriesInfo itemId, CancellationToken cancellationToken)
{
var result = new MetadataResult<Series>();
+ result.QueriedById = true;
if (!IsValidSeries(itemId.ProviderIds))
{
+ result.QueriedById = false;
await Identify(itemId).ConfigureAwait(false);
}
@@ -159,7 +164,7 @@ namespace MediaBrowser.Providers.TV
var seriesXmlPath = GetSeriesXmlPath(seriesProviderIds, metadataLanguage);
var actorsXmlPath = Path.Combine(seriesDataPath, "actors.xml");
- FetchSeriesInfo(series, seriesXmlPath, cancellationToken);
+ FetchSeriesInfo(result, seriesXmlPath, cancellationToken);
cancellationToken.ThrowIfCancellationRequested();
@@ -236,7 +241,7 @@ namespace MediaBrowser.Providers.TV
DeleteXmlFiles(seriesDataPath);
// Copy to memory stream because we need a seekable stream
- using (var ms = new MemoryStream())
+ using (var ms = _memoryStreamProvider.CreateNew())
{
await zipStream.CopyToAsync(ms).ConfigureAwait(false);
@@ -607,7 +612,7 @@ namespace MediaBrowser.Providers.TV
return name.Trim();
}
- private void FetchSeriesInfo(Series item, string seriesXmlPath, CancellationToken cancellationToken)
+ private void FetchSeriesInfo(MetadataResult<Series> result, string seriesXmlPath, CancellationToken cancellationToken)
{
var settings = new XmlReaderSettings
{
@@ -639,7 +644,7 @@ namespace MediaBrowser.Providers.TV
{
using (var subtree = reader.ReadSubtree())
{
- FetchDataFromSeriesNode(item, subtree, cancellationToken);
+ FetchDataFromSeriesNode(result, subtree, cancellationToken);
}
break;
}
@@ -667,9 +672,9 @@ namespace MediaBrowser.Providers.TV
}
}
- if (item.Status.HasValue && item.Status.Value == SeriesStatus.Ended && episiodeAirDates.Count > 0)
+ if (result.Item.Status.HasValue && result.Item.Status.Value == SeriesStatus.Ended && episiodeAirDates.Count > 0)
{
- item.EndDate = episiodeAirDates.Max();
+ result.Item.EndDate = episiodeAirDates.Max();
}
}
@@ -861,8 +866,10 @@ namespace MediaBrowser.Providers.TV
}
}
- private void FetchDataFromSeriesNode(Series item, XmlReader reader, CancellationToken cancellationToken)
+ private void FetchDataFromSeriesNode(MetadataResult<Series> result, XmlReader reader, CancellationToken cancellationToken)
{
+ Series item = result.Item;
+
reader.MoveToContent();
// Loop through each element
@@ -886,6 +893,12 @@ namespace MediaBrowser.Providers.TV
break;
}
+ case "Language":
+ {
+ result.ResultLanguage = (reader.ReadElementContentAsString() ?? string.Empty).Trim();
+ break;
+ }
+
case "Airs_DayOfWeek":
{
var val = reader.ReadElementContentAsString();
diff --git a/MediaBrowser.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs b/MediaBrowser.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs
index 3239b20b2..fae78b9bc 100644
--- a/MediaBrowser.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs
+++ b/MediaBrowser.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs
@@ -30,12 +30,12 @@ namespace MediaBrowser.Server.Implementations.Channels
return Task.FromResult<IEnumerable<MediaSourceInfo>>(new List<MediaSourceInfo>());
}
- public Task<MediaSourceInfo> OpenMediaSource(string openToken, CancellationToken cancellationToken)
+ public Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> OpenMediaSource(string openToken, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
- public Task CloseMediaSource(string liveStreamId, CancellationToken cancellationToken)
+ public Task CloseMediaSource(string liveStreamId)
{
throw new NotImplementedException();
}
diff --git a/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs b/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs
index b76cf46b0..1369efae1 100644
--- a/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs
+++ b/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs
@@ -1577,97 +1577,5 @@ namespace MediaBrowser.Server.Implementations.Channels
return await _libraryManager.GetNamedView(name, "channels", "zz_" + name, cancellationToken).ConfigureAwait(false);
}
-
- public async Task DownloadChannelItem(BaseItem item, string destination,
- IProgress<double> progress, CancellationToken cancellationToken)
- {
- var sources = await GetDynamicMediaSources(item, cancellationToken)
- .ConfigureAwait(false);
-
- var list = sources.Where(i => i.Protocol == MediaProtocol.Http).ToList();
-
- foreach (var source in list)
- {
- await TryDownloadChannelItem(source, item, destination, progress, cancellationToken).ConfigureAwait(false);
- return;
- }
- }
-
- private async Task TryDownloadChannelItem(MediaSourceInfo source,
- BaseItem item,
- string destination,
- IProgress<double> progress,
- CancellationToken cancellationToken)
- {
- var options = new HttpRequestOptions
- {
- CancellationToken = cancellationToken,
- Url = source.Path,
- Progress = new Progress<double>()
- };
-
- var channel = GetChannel(item.ChannelId);
- var channelProvider = GetChannelProvider(channel);
- var features = channelProvider.GetChannelFeatures();
-
- if (!features.SupportsContentDownloading)
- {
- throw new ArgumentException("The channel does not support downloading.");
- }
-
- var limit = features.DailyDownloadLimit;
-
- foreach (var header in source.RequiredHttpHeaders)
- {
- options.RequestHeaders[header.Key] = header.Value;
- }
-
- _fileSystem.CreateDirectory(Path.GetDirectoryName(destination));
-
- // Determine output extension
- var response = await _httpClient.GetTempFileResponse(options).ConfigureAwait(false);
-
- if (response.ContentType.StartsWith("text/html"))
- {
- throw new HttpException("File not found")
- {
- StatusCode = HttpStatusCode.NotFound
- };
- }
-
- if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase) && response.ContentType.StartsWith("video/", StringComparison.OrdinalIgnoreCase))
- {
- var extension = response.ContentType.Split('/')
- .Last()
- .Replace("quicktime", "mov", StringComparison.OrdinalIgnoreCase);
-
- destination += "." + extension;
- }
- else if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) && response.ContentType.StartsWith("audio/", StringComparison.OrdinalIgnoreCase))
- {
- var extension = response.ContentType.Replace("audio/mpeg", "audio/mp3", StringComparison.OrdinalIgnoreCase)
- .Split('/')
- .Last();
-
- destination += "." + extension;
- }
- else
- {
- _fileSystem.DeleteFile(response.TempFilePath);
-
- throw new ApplicationException("Unexpected response type encountered: " + response.ContentType);
- }
-
- _fileSystem.CopyFile(response.TempFilePath, destination, true);
-
- try
- {
- _fileSystem.DeleteFile(response.TempFilePath);
- }
- catch
- {
-
- }
- }
}
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs b/MediaBrowser.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs
index 005bbb852..5ac3da3db 100644
--- a/MediaBrowser.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs
+++ b/MediaBrowser.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs
@@ -35,7 +35,7 @@ namespace MediaBrowser.Server.Implementations.Channels
public string Category
{
- get { return "Channels"; }
+ get { return "Internet Channels"; }
}
public async Task Execute(System.Threading.CancellationToken cancellationToken, IProgress<double> progress)
diff --git a/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs b/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs
index 1b6c44c5e..cb2bd645d 100644
--- a/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs
+++ b/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs
@@ -219,7 +219,9 @@ namespace MediaBrowser.Server.Implementations.Collections
foreach (var itemId in itemIds)
{
- var child = collection.LinkedChildren.FirstOrDefault(i => i.ItemId.HasValue && i.ItemId.Value == itemId);
+ var childItem = _libraryManager.GetItemById(itemId);
+
+ var child = collection.LinkedChildren.FirstOrDefault(i => (i.ItemId.HasValue && i.ItemId.Value == itemId) || (childItem != null && string.Equals(childItem.Path, i.Path, StringComparison.OrdinalIgnoreCase)));
if (child == null)
{
@@ -228,47 +230,15 @@ namespace MediaBrowser.Server.Implementations.Collections
list.Add(child);
- var childItem = _libraryManager.GetItemById(itemId);
-
if (childItem != null)
{
itemList.Add(childItem);
}
}
- var shortcutFiles = _fileSystem
- .GetFilePaths(collection.Path)
- .Where(i => _fileSystem.IsShortcut(i))
- .ToList();
-
- var shortcutFilesToDelete = list.Where(child => !string.IsNullOrWhiteSpace(child.Path) && child.Type == LinkedChildType.Shortcut)
- .Select(child => shortcutFiles.FirstOrDefault(i => string.Equals(child.Path, _fileSystem.ResolveShortcut(i), StringComparison.OrdinalIgnoreCase)))
- .Where(i => !string.IsNullOrWhiteSpace(i))
- .ToList();
-
- foreach (var file in shortcutFilesToDelete)
- {
- _iLibraryMonitor.ReportFileSystemChangeBeginning(file);
- }
-
- try
- {
- foreach (var file in shortcutFilesToDelete)
- {
- _fileSystem.DeleteFile(file);
- }
-
- foreach (var child in list)
- {
- collection.LinkedChildren.Remove(child);
- }
- }
- finally
+ foreach (var child in list)
{
- foreach (var file in shortcutFilesToDelete)
- {
- _iLibraryMonitor.ReportFileSystemChangeComplete(file, false);
- }
+ collection.LinkedChildren.Remove(child);
}
collection.UpdateRatingToContent();
diff --git a/MediaBrowser.Server.Implementations/Connect/ConnectEntryPoint.cs b/MediaBrowser.Server.Implementations/Connect/ConnectEntryPoint.cs
index 2b2373a47..f9eff3c92 100644
--- a/MediaBrowser.Server.Implementations/Connect/ConnectEntryPoint.cs
+++ b/MediaBrowser.Server.Implementations/Connect/ConnectEntryPoint.cs
@@ -127,7 +127,8 @@ namespace MediaBrowser.Server.Implementations.Connect
// Seeing block length errors with our server
EnableHttpCompression = false,
- PreferIpv4 = preferIpv4
+ PreferIpv4 = preferIpv4,
+ BufferContent = false
}).ConfigureAwait(false))
{
diff --git a/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs b/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs
index 45cb2f57d..d7c1b0da0 100644
--- a/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs
+++ b/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs
@@ -266,7 +266,8 @@ namespace MediaBrowser.Server.Implementations.Connect
var options = new HttpRequestOptions
{
Url = url,
- CancellationToken = CancellationToken.None
+ CancellationToken = CancellationToken.None,
+ BufferContent = false
};
options.SetPostData(postData);
@@ -314,7 +315,8 @@ namespace MediaBrowser.Server.Implementations.Connect
var options = new HttpRequestOptions
{
Url = url,
- CancellationToken = CancellationToken.None
+ CancellationToken = CancellationToken.None,
+ BufferContent = false
};
options.SetPostData(postData);
@@ -403,6 +405,14 @@ namespace MediaBrowser.Server.Implementations.Connect
public async Task<UserLinkResult> LinkUser(string userId, string connectUsername)
{
+ if (string.IsNullOrWhiteSpace(userId))
+ {
+ throw new ArgumentNullException("userId");
+ }
+ if (string.IsNullOrWhiteSpace(connectUsername))
+ {
+ throw new ArgumentNullException("connectUsername");
+ }
if (string.IsNullOrWhiteSpace(ConnectServerId))
{
await UpdateConnectInfo().ConfigureAwait(false);
@@ -422,14 +432,6 @@ namespace MediaBrowser.Server.Implementations.Connect
private async Task<UserLinkResult> LinkUserInternal(string userId, string connectUsername)
{
- if (string.IsNullOrWhiteSpace(userId))
- {
- throw new ArgumentNullException("userId");
- }
- if (string.IsNullOrWhiteSpace(connectUsername))
- {
- throw new ArgumentNullException("connectUsername");
- }
if (string.IsNullOrWhiteSpace(ConnectServerId))
{
throw new ArgumentNullException("ConnectServerId");
@@ -446,11 +448,17 @@ namespace MediaBrowser.Server.Implementations.Connect
throw new ArgumentException("The Emby account has been disabled.");
}
+ var existingUser = _userManager.Users.FirstOrDefault(i => string.Equals(i.ConnectUserId, connectUser.Id) && !string.IsNullOrWhiteSpace(i.ConnectAccessKey));
+ if (existingUser != null)
+ {
+ throw new InvalidOperationException("This connect user is already linked to local user " + existingUser.Name);
+ }
+
var user = GetUser(userId);
if (!string.IsNullOrWhiteSpace(user.ConnectUserId))
{
- await RemoveConnect(user, connectUser.Id).ConfigureAwait(false);
+ await RemoveConnect(user, user.ConnectUserId).ConfigureAwait(false);
}
var url = GetConnectUrl("ServerAuthorizations");
@@ -458,7 +466,8 @@ namespace MediaBrowser.Server.Implementations.Connect
var options = new HttpRequestOptions
{
Url = url,
- CancellationToken = CancellationToken.None
+ CancellationToken = CancellationToken.None,
+ BufferContent = false
};
var accessToken = Guid.NewGuid().ToString("N");
@@ -593,7 +602,8 @@ namespace MediaBrowser.Server.Implementations.Connect
var options = new HttpRequestOptions
{
Url = url,
- CancellationToken = CancellationToken.None
+ CancellationToken = CancellationToken.None,
+ BufferContent = false
};
var accessToken = Guid.NewGuid().ToString("N");
@@ -646,7 +656,8 @@ namespace MediaBrowser.Server.Implementations.Connect
var options = new HttpRequestOptions
{
Url = url,
- CancellationToken = CancellationToken.None
+ CancellationToken = CancellationToken.None,
+ BufferContent = false
};
var postData = new Dictionary<string, string>
@@ -720,7 +731,8 @@ namespace MediaBrowser.Server.Implementations.Connect
var options = new HttpRequestOptions
{
CancellationToken = cancellationToken,
- Url = url
+ Url = url,
+ BufferContent = false
};
SetServerAccessToken(options);
@@ -784,7 +796,8 @@ namespace MediaBrowser.Server.Implementations.Connect
var options = new HttpRequestOptions
{
Url = url,
- CancellationToken = cancellationToken
+ CancellationToken = cancellationToken,
+ BufferContent = false
};
SetServerAccessToken(options);
@@ -1072,7 +1085,8 @@ namespace MediaBrowser.Server.Implementations.Connect
var options = new HttpRequestOptions
{
Url = url,
- CancellationToken = CancellationToken.None
+ CancellationToken = CancellationToken.None,
+ BufferContent = false
};
var postData = new Dictionary<string, string>
@@ -1120,7 +1134,8 @@ namespace MediaBrowser.Server.Implementations.Connect
var options = new HttpRequestOptions
{
- Url = GetConnectUrl("user/authenticate")
+ Url = GetConnectUrl("user/authenticate"),
+ BufferContent = false
};
options.SetPostData(new Dictionary<string, string>
diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs
index 9284f4fc7..a0f7aa999 100644
--- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs
+++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs
@@ -437,6 +437,7 @@ namespace MediaBrowser.Server.Implementations.Dto
dto.TrailerCount = taggedItems.Count(i => i is Trailer);
dto.MusicVideoCount = taggedItems.Count(i => i is MusicVideo);
dto.SeriesCount = taggedItems.Count(i => i is Series);
+ dto.ProgramCount = taggedItems.Count(i => i is LiveTvProgram);
dto.SongCount = taggedItems.Count(i => i is Audio);
}
@@ -597,7 +598,7 @@ namespace MediaBrowser.Server.Implementations.Dto
dto.Altitude = item.Altitude;
dto.IsoSpeedRating = item.IsoSpeedRating;
- var album = item.Album;
+ var album = item.AlbumEntity;
if (album != null)
{
@@ -906,11 +907,6 @@ namespace MediaBrowser.Server.Implementations.Dto
dto.Keywords = item.Keywords;
}
- if (fields.Contains(ItemFields.ProductionLocations))
- {
- SetProductionLocations(item, dto);
- }
-
var hasAspectRatio = item as IHasAspectRatio;
if (hasAspectRatio != null)
{
@@ -997,8 +993,11 @@ namespace MediaBrowser.Server.Implementations.Dto
}
dto.Audio = item.Audio;
- dto.PreferredMetadataCountryCode = item.PreferredMetadataCountryCode;
- dto.PreferredMetadataLanguage = item.PreferredMetadataLanguage;
+ if (fields.Contains(ItemFields.Settings))
+ {
+ dto.PreferredMetadataCountryCode = item.PreferredMetadataCountryCode;
+ dto.PreferredMetadataLanguage = item.PreferredMetadataLanguage;
+ }
dto.CriticRating = item.CriticRating;
@@ -1088,10 +1087,9 @@ namespace MediaBrowser.Server.Implementations.Dto
if (fields.Contains(ItemFields.Taglines))
{
- var hasTagline = item as IHasTaglines;
- if (hasTagline != null)
+ if (!string.IsNullOrWhiteSpace(item.Tagline))
{
- dto.Taglines = hasTagline.Taglines;
+ dto.Taglines = new List<string> { item.Tagline };
}
if (dto.Taglines == null)
@@ -1175,6 +1173,12 @@ namespace MediaBrowser.Server.Implementations.Dto
.Except(foundArtists, new DistinctNameComparer())
.Select(i =>
{
+ // This should not be necessary but we're seeing some cases of it
+ if (string.IsNullOrWhiteSpace(i))
+ {
+ return null;
+ }
+
var artist = _libraryManager.GetArtist(i);
if (artist != null)
{
@@ -1419,6 +1423,11 @@ namespace MediaBrowser.Server.Implementations.Dto
SetBookProperties(dto, book);
}
+ if (item.ProductionLocations.Count > 0 || item is Movie)
+ {
+ dto.ProductionLocations = item.ProductionLocations.ToArray();
+ }
+
var photo = item as Photo;
if (photo != null)
{
@@ -1508,7 +1517,7 @@ namespace MediaBrowser.Server.Implementations.Dto
}
}
- private string GetMappedPath(IHasMetadata item)
+ private string GetMappedPath(BaseItem item)
{
var path = item.Path;
@@ -1516,40 +1525,12 @@ namespace MediaBrowser.Server.Implementations.Dto
if (locationType == LocationType.FileSystem || locationType == LocationType.Offline)
{
- foreach (var map in _config.Configuration.PathSubstitutions)
- {
- path = _libraryManager.SubstitutePath(path, map.From, map.To);
- }
+ path = _libraryManager.GetPathAfterNetworkSubstitution(path, item);
}
return path;
}
- private void SetProductionLocations(BaseItem item, BaseItemDto dto)
- {
- var hasProductionLocations = item as IHasProductionLocations;
-
- if (hasProductionLocations != null)
- {
- dto.ProductionLocations = hasProductionLocations.ProductionLocations;
- }
-
- var person = item as Person;
- if (person != null)
- {
- dto.ProductionLocations = new List<string>();
- if (!string.IsNullOrEmpty(person.PlaceOfBirth))
- {
- dto.ProductionLocations.Add(person.PlaceOfBirth);
- }
- }
-
- if (dto.ProductionLocations == null)
- {
- dto.ProductionLocations = new List<string>();
- }
- }
-
/// <summary>
/// Attaches the primary image aspect ratio.
/// </summary>
diff --git a/MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs b/MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs
index 280bec65b..1021d8823 100644
--- a/MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs
+++ b/MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs
@@ -9,6 +9,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.Net;
using MediaBrowser.Common.Threading;
+using MediaBrowser.Model.Events;
namespace MediaBrowser.Server.Implementations.EntryPoints
{
@@ -17,17 +18,17 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
private readonly IServerApplicationHost _appHost;
private readonly ILogger _logger;
private readonly IServerConfigurationManager _config;
- private readonly ISsdpHandler _ssdp;
+ private readonly IDeviceDiscovery _deviceDiscovery;
private PeriodicTimer _timer;
private bool _isStarted;
- public ExternalPortForwarding(ILogManager logmanager, IServerApplicationHost appHost, IServerConfigurationManager config, ISsdpHandler ssdp)
+ public ExternalPortForwarding(ILogManager logmanager, IServerApplicationHost appHost, IServerConfigurationManager config, IDeviceDiscovery deviceDiscovery)
{
_logger = logmanager.GetLogger("PortMapper");
_appHost = appHost;
_config = config;
- _ssdp = ssdp;
+ _deviceDiscovery = deviceDiscovery;
}
private string _lastConfigIdentifier;
@@ -61,7 +62,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
public void Run()
{
- //NatUtility.Logger = new LogWriter(_logger);
+ NatUtility.Logger = _logger;
if (_config.Configuration.EnableUPnP)
{
@@ -93,33 +94,22 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
_timer = new PeriodicTimer(ClearCreatedRules, null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5));
- _ssdp.MessageReceived += _ssdp_MessageReceived;
+ _deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered;
_lastConfigIdentifier = GetConfigIdentifier();
_isStarted = true;
}
- private void ClearCreatedRules(object state)
- {
- _createdRules = new List<string>();
- _usnsHandled = new List<string>();
- }
-
- void _ssdp_MessageReceived(object sender, SsdpMessageEventArgs e)
+ private async void _deviceDiscovery_DeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
{
- var endpoint = e.EndPoint as IPEndPoint;
-
- if (endpoint == null || e.LocalEndPoint == null)
- {
- return;
- }
+ var info = e.Argument;
string usn;
- if (!e.Headers.TryGetValue("USN", out usn)) usn = string.Empty;
+ if (!info.Headers.TryGetValue("USN", out usn)) usn = string.Empty;
string nt;
- if (!e.Headers.TryGetValue("NT", out nt)) nt = string.Empty;
+ if (!info.Headers.TryGetValue("NT", out nt)) nt = string.Empty;
// Filter device type
if (usn.IndexOf("WANIPConnection:", StringComparison.OrdinalIgnoreCase) == -1 &&
@@ -132,15 +122,45 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
var identifier = string.IsNullOrWhiteSpace(usn) ? nt : usn;
- if (!_usnsHandled.Contains(identifier))
+ if (info.Location != null && !_usnsHandled.Contains(identifier))
{
_usnsHandled.Add(identifier);
_logger.Debug("Calling Nat.Handle on " + identifier);
- NatUtility.Handle(e.LocalEndPoint.Address, e.Message, endpoint, NatProtocol.Upnp);
+
+ IPAddress address;
+ if (IPAddress.TryParse(info.Location.Host, out address))
+ {
+ // The Handle method doesn't need the port
+ var endpoint = new IPEndPoint(address, info.Location.Port);
+
+ IPAddress localAddress = null;
+
+ try
+ {
+ var localAddressString = await _appHost.GetLocalApiUrl().ConfigureAwait(false);
+
+ if (!IPAddress.TryParse(localAddressString, out localAddress))
+ {
+ return;
+ }
+ }
+ catch
+ {
+ return;
+ }
+
+ NatUtility.Handle(localAddress, info, endpoint, NatProtocol.Upnp);
+ }
}
}
+ private void ClearCreatedRules(object state)
+ {
+ _createdRules = new List<string>();
+ _usnsHandled = new List<string>();
+ }
+
void NatUtility_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
var ex = e.ExceptionObject as Exception;
@@ -228,7 +248,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
_timer = null;
}
- _ssdp.MessageReceived -= _ssdp_MessageReceived;
+ _deviceDiscovery.DeviceDiscovered -= _deviceDiscovery_DeviceDiscovered;
try
{
diff --git a/MediaBrowser.Server.Implementations/EntryPoints/Notifications/Notifications.cs b/MediaBrowser.Server.Implementations/EntryPoints/Notifications/Notifications.cs
index e84b66c5a..f7fe707da 100644
--- a/MediaBrowser.Server.Implementations/EntryPoints/Notifications/Notifications.cs
+++ b/MediaBrowser.Server.Implementations/EntryPoints/Notifications/Notifications.cs
@@ -377,10 +377,10 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
DisposeLibraryUpdateTimer();
}
- if (items.Count == 1)
- {
- var item = items.First();
+ items = items.Take(10).ToList();
+ foreach (var item in items)
+ {
var notification = new NotificationRequest
{
NotificationType = NotificationType.NewLibraryContent.ToString()
@@ -390,17 +390,6 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
await SendNotification(notification).ConfigureAwait(false);
}
- else
- {
- var notification = new NotificationRequest
- {
- NotificationType = NotificationType.NewLibraryContentMultiple.ToString()
- };
-
- notification.Variables["ItemCount"] = items.Count.ToString(CultureInfo.InvariantCulture);
-
- await SendNotification(notification).ConfigureAwait(false);
- }
}
public static string GetItemName(BaseItem item)
diff --git a/MediaBrowser.Server.Implementations/EntryPoints/UsageReporter.cs b/MediaBrowser.Server.Implementations/EntryPoints/UsageReporter.cs
index 7b3a7a30d..e445300e4 100644
--- a/MediaBrowser.Server.Implementations/EntryPoints/UsageReporter.cs
+++ b/MediaBrowser.Server.Implementations/EntryPoints/UsageReporter.cs
@@ -64,7 +64,8 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
EnableHttpCompression = false,
LogRequest = false,
- LogErrors = logErrors
+ LogErrors = logErrors,
+ BufferContent = false
};
options.SetPostData(data);
@@ -114,7 +115,8 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
EnableHttpCompression = false,
LogRequest = false,
- LogErrors = logErrors
+ LogErrors = logErrors,
+ BufferContent = false
};
options.SetPostData(data);
diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs
index 8e46f8f03..9ec671908 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs
@@ -19,6 +19,7 @@ using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net;
using MediaBrowser.Common.Security;
using MediaBrowser.Model.Extensions;
@@ -45,16 +46,18 @@ namespace MediaBrowser.Server.Implementations.HttpServer
private readonly IServerConfigurationManager _config;
private readonly INetworkManager _networkManager;
+ private readonly IMemoryStreamProvider _memoryStreamProvider;
public HttpListenerHost(IApplicationHost applicationHost,
ILogManager logManager,
IServerConfigurationManager config,
string serviceName,
- string defaultRedirectPath, INetworkManager networkManager, params Assembly[] assembliesWithServices)
+ string defaultRedirectPath, INetworkManager networkManager, IMemoryStreamProvider memoryStreamProvider, params Assembly[] assembliesWithServices)
: base(serviceName, assembliesWithServices)
{
DefaultRedirectPath = defaultRedirectPath;
_networkManager = networkManager;
+ _memoryStreamProvider = memoryStreamProvider;
_config = config;
_logger = logManager.GetLogger("HttpServer");
@@ -88,14 +91,16 @@ namespace MediaBrowser.Server.Implementations.HttpServer
HostConfig.Instance.DebugMode = false;
HostConfig.Instance.LogFactory = LogManager.LogFactory;
+ HostConfig.Instance.AllowJsonpRequests = false;
// The Markdown feature causes slow startup times (5 mins+) on cold boots for some users
// Custom format allows images
- HostConfig.Instance.EnableFeatures = Feature.Csv | Feature.Html | Feature.Json | Feature.Jsv | Feature.Metadata | Feature.Xml | Feature.CustomFormat;
+ HostConfig.Instance.EnableFeatures = Feature.Html | Feature.Json | Feature.Xml | Feature.CustomFormat;
container.Adapter = _containerAdapter;
- Plugins.Add(new SwaggerFeature());
+ Plugins.RemoveAll(x => x is NativeTypesFeature);
+ //Plugins.Add(new SwaggerFeature());
Plugins.Add(new CorsFeature(allowedHeaders: "Content-Type, Authorization, Range, X-MediaBrowser-Token, X-Emby-Authorization"));
//Plugins.Add(new AuthFeature(() => new AuthUserSession(), new IAuthProvider[] {
@@ -179,7 +184,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
private IHttpListener GetListener()
{
- return new WebSocketSharpListener(_logger, CertificatePath);
+ return new WebSocketSharpListener(_logger, CertificatePath, _memoryStreamProvider);
}
private void OnWebSocketConnecting(WebSocketConnectingEventArgs args)
@@ -542,8 +547,10 @@ namespace MediaBrowser.Server.Implementations.HttpServer
}
}
}
-
- throw new NotImplementedException("Cannot execute handler: " + handler + " at PathInfo: " + httpReq.PathInfo);
+ else
+ {
+ httpRes.Close();
+ }
}
/// <summary>
diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs
index c55e98388..10d6f7493 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs
@@ -93,12 +93,14 @@ namespace MediaBrowser.Server.Implementations.HttpServer
}
}
}
-
- if (responseHeaders != null)
+ if (responseHeaders == null)
{
- AddResponseHeaders(result, responseHeaders);
+ responseHeaders = new Dictionary<string, string>();
}
+ responseHeaders["Expires"] = "-1";
+ AddResponseHeaders(result, responseHeaders);
+
return result;
}
@@ -681,29 +683,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer
}
}
- /// <summary>
- /// Gets the error result.
- /// </summary>
- /// <param name="statusCode">The status code.</param>
- /// <param name="errorMessage">The error message.</param>
- /// <param name="responseHeaders">The response headers.</param>
- /// <returns>System.Object.</returns>
- public void ThrowError(int statusCode, string errorMessage, IDictionary<string, string> responseHeaders = null)
- {
- var error = new HttpError
- {
- Status = statusCode,
- ErrorCode = errorMessage
- };
-
- if (responseHeaders != null)
- {
- AddResponseHeaders(error, responseHeaders);
- }
-
- throw error;
- }
-
public object GetAsyncStreamWriter(IAsyncStreamSource streamSource)
{
return new AsyncStreamWriter(streamSource);
diff --git a/MediaBrowser.Server.Implementations/HttpServer/NativeWebSocket.cs b/MediaBrowser.Server.Implementations/HttpServer/NativeWebSocket.cs
deleted file mode 100644
index cac2f8e09..000000000
--- a/MediaBrowser.Server.Implementations/HttpServer/NativeWebSocket.cs
+++ /dev/null
@@ -1,240 +0,0 @@
-using MediaBrowser.Common.Events;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Model.Logging;
-using System;
-using System.Net.WebSockets;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using WebSocketMessageType = MediaBrowser.Model.Net.WebSocketMessageType;
-using WebSocketState = MediaBrowser.Model.Net.WebSocketState;
-
-namespace MediaBrowser.Server.Implementations.HttpServer
-{
- /// <summary>
- /// Class NativeWebSocket
- /// </summary>
- public class NativeWebSocket : IWebSocket
- {
- /// <summary>
- /// The logger
- /// </summary>
- private readonly ILogger _logger;
-
- public event EventHandler<EventArgs> Closed;
-
- /// <summary>
- /// Gets or sets the web socket.
- /// </summary>
- /// <value>The web socket.</value>
- private System.Net.WebSockets.WebSocket WebSocket { get; set; }
-
- private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
-
- /// <summary>
- /// Initializes a new instance of the <see cref="NativeWebSocket" /> class.
- /// </summary>
- /// <param name="socket">The socket.</param>
- /// <param name="logger">The logger.</param>
- /// <exception cref="System.ArgumentNullException">socket</exception>
- public NativeWebSocket(WebSocket socket, ILogger logger)
- {
- if (socket == null)
- {
- throw new ArgumentNullException("socket");
- }
-
- if (logger == null)
- {
- throw new ArgumentNullException("logger");
- }
-
- _logger = logger;
- WebSocket = socket;
-
- Receive();
- }
-
- /// <summary>
- /// Gets or sets the state.
- /// </summary>
- /// <value>The state.</value>
- public WebSocketState State
- {
- get
- {
- WebSocketState commonState;
-
- if (!Enum.TryParse(WebSocket.State.ToString(), true, out commonState))
- {
- _logger.Warn("Unrecognized WebSocketState: {0}", WebSocket.State.ToString());
- }
-
- return commonState;
- }
- }
-
- /// <summary>
- /// Receives this instance.
- /// </summary>
- private async void Receive()
- {
- while (true)
- {
- byte[] bytes;
-
- try
- {
- bytes = await ReceiveBytesAsync(_cancellationTokenSource.Token).ConfigureAwait(false);
- }
- catch (OperationCanceledException)
- {
- break;
- }
- catch (WebSocketException ex)
- {
- _logger.ErrorException("Error receiving web socket message", ex);
-
- break;
- }
-
- if (bytes == null)
- {
- // Connection closed
- EventHelper.FireEventIfNotNull(Closed, this, EventArgs.Empty, _logger);
- break;
- }
-
- if (OnReceiveBytes != null)
- {
- OnReceiveBytes(bytes);
- }
- }
- }
-
- /// <summary>
- /// Receives the async.
- /// </summary>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task{WebSocketMessageInfo}.</returns>
- /// <exception cref="System.Net.WebSockets.WebSocketException">Connection closed</exception>
- private async Task<byte[]> ReceiveBytesAsync(CancellationToken cancellationToken)
- {
- var bytes = new byte[4096];
- var buffer = new ArraySegment<byte>(bytes);
-
- var result = await WebSocket.ReceiveAsync(buffer, cancellationToken).ConfigureAwait(false);
-
- if (result.CloseStatus.HasValue)
- {
- _logger.Info("Web socket connection closed by client. Reason: {0}", result.CloseStatus.Value);
- return null;
- }
-
- return buffer.Array;
- }
-
- /// <summary>
- /// Sends the async.
- /// </summary>
- /// <param name="bytes">The bytes.</param>
- /// <param name="type">The type.</param>
- /// <param name="endOfMessage">if set to <c>true</c> [end of message].</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
- public Task SendAsync(byte[] bytes, WebSocketMessageType type, bool endOfMessage, CancellationToken cancellationToken)
- {
- System.Net.WebSockets.WebSocketMessageType nativeType;
-
- if (!Enum.TryParse(type.ToString(), true, out nativeType))
- {
- _logger.Warn("Unrecognized WebSocketMessageType: {0}", type.ToString());
- }
-
- var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _cancellationTokenSource.Token);
-
- return WebSocket.SendAsync(new ArraySegment<byte>(bytes), nativeType, true, linkedTokenSource.Token);
- }
-
- public Task SendAsync(byte[] bytes, bool endOfMessage, CancellationToken cancellationToken)
- {
- var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _cancellationTokenSource.Token);
-
- return WebSocket.SendAsync(new ArraySegment<byte>(bytes), System.Net.WebSockets.WebSocketMessageType.Binary, true, linkedTokenSource.Token);
- }
-
- public Task SendAsync(string text, bool endOfMessage, CancellationToken cancellationToken)
- {
- var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _cancellationTokenSource.Token);
-
- var bytes = Encoding.UTF8.GetBytes(text);
-
- return WebSocket.SendAsync(new ArraySegment<byte>(bytes), System.Net.WebSockets.WebSocketMessageType.Text, true, linkedTokenSource.Token);
- }
-
- /// <summary>
- /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
- /// </summary>
- public void Dispose()
- {
- Dispose(true);
- }
-
- /// <summary>
- /// Releases unmanaged and - optionally - managed resources.
- /// </summary>
- /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
- protected virtual void Dispose(bool dispose)
- {
- if (dispose)
- {
- _cancellationTokenSource.Cancel();
-
- WebSocket.Dispose();
- }
- }
-
- /// <summary>
- /// Gets or sets the receive action.
- /// </summary>
- /// <value>The receive action.</value>
- public Action<byte[]> OnReceiveBytes { get; set; }
-
- /// <summary>
- /// Gets or sets the on receive.
- /// </summary>
- /// <value>The on receive.</value>
- public Action<string> OnReceive { get; set; }
-
- /// <summary>
- /// The _supports native web socket
- /// </summary>
- private static bool? _supportsNativeWebSocket;
-
- /// <summary>
- /// Gets a value indicating whether [supports web sockets].
- /// </summary>
- /// <value><c>true</c> if [supports web sockets]; otherwise, <c>false</c>.</value>
- public static bool IsSupported
- {
- get
- {
- if (!_supportsNativeWebSocket.HasValue)
- {
- try
- {
- new ClientWebSocket();
-
- _supportsNativeWebSocket = true;
- }
- catch (PlatformNotSupportedException)
- {
- _supportsNativeWebSocket = false;
- }
- }
-
- return _supportsNativeWebSocket.Value;
- }
- }
- }
-}
diff --git a/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs b/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs
index 488c630fe..4b94095f5 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs
@@ -191,15 +191,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer
}
}
}
- catch (IOException)
- {
- throw;
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error in range request writer", ex);
- throw;
- }
finally
{
if (OnComplete != null)
@@ -251,15 +242,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer
}
}
}
- catch (IOException ex)
- {
- throw;
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error in range request writer", ex);
- throw;
- }
finally
{
if (OnComplete != null)
diff --git a/MediaBrowser.Server.Implementations/HttpServer/ServerFactory.cs b/MediaBrowser.Server.Implementations/HttpServer/ServerFactory.cs
index cc351f6b3..8a7c14eb6 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/ServerFactory.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/ServerFactory.cs
@@ -1,4 +1,5 @@
using MediaBrowser.Common;
+using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Net;
@@ -15,23 +16,18 @@ namespace MediaBrowser.Server.Implementations.HttpServer
/// <summary>
/// Creates the server.
/// </summary>
- /// <param name="applicationHost">The application host.</param>
- /// <param name="logManager">The log manager.</param>
- /// <param name="config">The configuration.</param>
- /// <param name="_networkmanager">The _networkmanager.</param>
- /// <param name="serverName">Name of the server.</param>
- /// <param name="defaultRedirectpath">The default redirectpath.</param>
/// <returns>IHttpServer.</returns>
public static IHttpServer CreateServer(IApplicationHost applicationHost,
ILogManager logManager,
IServerConfigurationManager config,
INetworkManager _networkmanager,
+ IMemoryStreamProvider streamProvider,
string serverName,
string defaultRedirectpath)
{
LogManager.LogFactory = new ServerLogFactory(logManager);
- return new HttpListenerHost(applicationHost, logManager, config, serverName, defaultRedirectpath, _networkmanager);
+ return new HttpListenerHost(applicationHost, logManager, config, serverName, defaultRedirectpath, _networkmanager, streamProvider);
}
}
}
diff --git a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/RequestMono.cs b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/RequestMono.cs
index bfa65ac6b..d20dd7ec0 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/RequestMono.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/RequestMono.cs
@@ -39,11 +39,11 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
if (boundary == null)
return;
- using (var requestStream = GetSubStream(InputStream))
+ using (var requestStream = GetSubStream(InputStream, _memoryStreamProvider))
{
//DB: 30/01/11 - Hack to get around non-seekable stream and received HTTP request
//Not ending with \r\n?
- var ms = new MemoryStream(32 * 1024);
+ var ms = _memoryStreamProvider.CreateNew(32 * 1024);
await requestStream.CopyToAsync(ms).ConfigureAwait(false);
var input = ms;
@@ -229,9 +229,9 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
async Task LoadWwwForm()
{
- using (Stream input = GetSubStream(InputStream))
+ using (Stream input = GetSubStream(InputStream, _memoryStreamProvider))
{
- using (var ms = new MemoryStream())
+ using (var ms = _memoryStreamProvider.CreateNew())
{
await input.CopyToAsync(ms).ConfigureAwait(false);
ms.Position = 0;
diff --git a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs
index bcc081eb1..b090c97c6 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs
@@ -9,6 +9,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
+using MediaBrowser.Common.IO;
namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
{
@@ -18,11 +19,13 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
private readonly ILogger _logger;
private readonly string _certificatePath;
+ private readonly IMemoryStreamProvider _memoryStreamProvider;
- public WebSocketSharpListener(ILogger logger, string certificatePath)
+ public WebSocketSharpListener(ILogger logger, string certificatePath, IMemoryStreamProvider memoryStreamProvider)
{
_logger = logger;
_certificatePath = certificatePath;
+ _memoryStreamProvider = memoryStreamProvider;
}
public Action<Exception, IRequest> ErrorHandler { get; set; }
@@ -148,7 +151,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
{
var operationName = httpContext.Request.GetOperationName();
- var req = new WebSocketSharpRequest(httpContext, operationName, RequestAttributes.None, _logger);
+ var req = new WebSocketSharpRequest(httpContext, operationName, RequestAttributes.None, _logger, _memoryStreamProvider);
req.RequestAttributes = req.GetAttributes();
return req;
diff --git a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs
index dc2aec3e1..b5c8d0107 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Text;
using Funq;
+using MediaBrowser.Common.IO;
using MediaBrowser.Model.Logging;
using ServiceStack;
using ServiceStack.Host;
@@ -16,11 +17,13 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
public Container Container { get; set; }
private readonly HttpListenerRequest request;
private readonly IHttpResponse response;
+ private readonly IMemoryStreamProvider _memoryStreamProvider;
- public WebSocketSharpRequest(HttpListenerContext httpContext, string operationName, RequestAttributes requestAttributes, ILogger logger)
+ public WebSocketSharpRequest(HttpListenerContext httpContext, string operationName, RequestAttributes requestAttributes, ILogger logger, IMemoryStreamProvider memoryStreamProvider)
{
this.OperationName = operationName;
this.RequestAttributes = requestAttributes;
+ _memoryStreamProvider = memoryStreamProvider;
this.request = httpContext.Request;
this.response = new WebSocketSharpResponse(logger, httpContext.Response, this);
@@ -403,7 +406,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
set
{
bufferedStream = value
- ? bufferedStream ?? new MemoryStream(request.InputStream.ReadFully())
+ ? bufferedStream ?? _memoryStreamProvider.CreateNew(request.InputStream.ReadFully())
: null;
}
}
@@ -447,7 +450,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
}
}
- static Stream GetSubStream(Stream stream)
+ static Stream GetSubStream(Stream stream, IMemoryStreamProvider streamProvider)
{
if (stream is MemoryStream)
{
diff --git a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs
index e08be8bd1..a58645ec5 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs
@@ -81,20 +81,12 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
public void Write(string text)
{
- try
- {
- var bOutput = System.Text.Encoding.UTF8.GetBytes(text);
- response.ContentLength64 = bOutput.Length;
+ var bOutput = System.Text.Encoding.UTF8.GetBytes(text);
+ response.ContentLength64 = bOutput.Length;
- var outputStream = response.OutputStream;
- outputStream.Write(bOutput, 0, bOutput.Length);
- Close();
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Could not WriteTextToResponse: " + ex.Message, ex);
- throw;
- }
+ var outputStream = response.OutputStream;
+ outputStream.Write(bOutput, 0, bOutput.Length);
+ Close();
}
public void Close()
diff --git a/MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs b/MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs
index ae408f8d6..5f122fb96 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs
@@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Threading.Tasks;
+using MediaBrowser.Common.IO;
using ServiceStack;
namespace MediaBrowser.Server.Implementations.HttpServer
@@ -17,7 +18,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
private ILogger Logger { get; set; }
private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
-
+
/// <summary>
/// Gets or sets the source stream.
/// </summary>
@@ -39,6 +40,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
public Action OnComplete { get; set; }
public Action OnError { get; set; }
+ private readonly byte[] _bytes;
/// <summary>
/// Initializes a new instance of the <see cref="StreamWriter" /> class.
@@ -73,6 +75,17 @@ namespace MediaBrowser.Server.Implementations.HttpServer
public StreamWriter(byte[] source, string contentType, ILogger logger)
: this(new MemoryStream(source), contentType, logger)
{
+ if (string.IsNullOrEmpty(contentType))
+ {
+ throw new ArgumentNullException("contentType");
+ }
+
+ _bytes = source;
+ Logger = logger;
+
+ Options["Content-Type"] = contentType;
+
+ Options["Content-Length"] = source.Length.ToString(UsCulture);
}
private const int BufferSize = 81920;
@@ -85,9 +98,16 @@ namespace MediaBrowser.Server.Implementations.HttpServer
{
try
{
- using (var src = SourceStream)
+ if (_bytes != null)
{
- src.CopyTo(responseStream, BufferSize);
+ responseStream.Write(_bytes, 0, _bytes.Length);
+ }
+ else
+ {
+ using (var src = SourceStream)
+ {
+ src.CopyTo(responseStream, BufferSize);
+ }
}
}
catch (Exception ex)
@@ -114,9 +134,16 @@ namespace MediaBrowser.Server.Implementations.HttpServer
{
try
{
- using (var src = SourceStream)
+ if (_bytes != null)
+ {
+ await responseStream.WriteAsync(_bytes, 0, _bytes.Length);
+ }
+ else
{
- await src.CopyToAsync(responseStream, BufferSize).ConfigureAwait(false);
+ using (var src = SourceStream)
+ {
+ await src.CopyToAsync(responseStream, BufferSize).ConfigureAwait(false);
+ }
}
}
catch (Exception ex)
diff --git a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs
index dffaec458..76f0e6a1d 100644
--- a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs
+++ b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs
@@ -172,32 +172,16 @@ namespace MediaBrowser.Server.Implementations.IO
Start();
}
- private bool EnableLibraryMonitor
- {
- get
- {
- switch (ConfigurationManager.Configuration.EnableLibraryMonitor)
- {
- case AutoOnOff.Auto:
- return Environment.OSVersion.Platform == PlatformID.Win32NT;
- case AutoOnOff.Enabled:
- return true;
- default:
- return false;
- }
- }
- }
-
private bool IsLibraryMonitorEnabaled(BaseItem item)
{
var options = LibraryManager.GetLibraryOptions(item);
- if (options != null && options.SchemaVersion >= 1)
+ if (options != null)
{
return options.EnableRealtimeMonitor;
}
- return EnableLibraryMonitor;
+ return false;
}
public void Start()
diff --git a/MediaBrowser.Server.Implementations/Intros/DefaultIntroProvider.cs b/MediaBrowser.Server.Implementations/Intros/DefaultIntroProvider.cs
index 4a43befed..9b23d5be4 100644
--- a/MediaBrowser.Server.Implementations/Intros/DefaultIntroProvider.cs
+++ b/MediaBrowser.Server.Implementations/Intros/DefaultIntroProvider.cs
@@ -99,8 +99,9 @@ namespace MediaBrowser.Server.Implementations.Intros
IncludeItemTypes = new[] { typeof(Trailer).Name },
TrailerTypes = trailerTypes.ToArray(),
SimilarTo = item,
- IsPlayed = config.EnableIntrosForWatchedContent ? (bool?) null : false,
+ IsPlayed = config.EnableIntrosForWatchedContent ? (bool?)null : false,
MaxParentalRating = config.EnableIntrosParentalControl ? ratingLevel : null,
+ BlockUnratedItems = config.EnableIntrosParentalControl ? new[] { UnratedItem.Trailer } : new UnratedItem[] { },
Limit = config.TrailerLimit
});
@@ -110,7 +111,7 @@ namespace MediaBrowser.Server.Implementations.Intros
Type = i.SourceType == SourceType.Channel ? ItemWithTrailerType.ChannelTrailer : ItemWithTrailerType.ItemWithTrailer,
LibraryManager = _libraryManager
}));
- }
+ }
return GetResult(item, candidates, config);
}
@@ -197,7 +198,7 @@ namespace MediaBrowser.Server.Implementations.Intros
}
returnResult.AddRange(GetMediaInfoIntrosByTags(allIntros, item.Tags).Take(1));
-
+
return returnResult.DistinctBy(i => i.Path, StringComparer.OrdinalIgnoreCase);
}
catch (IOException)
diff --git a/MediaBrowser.Server.Implementations/Library/CoreResolutionIgnoreRule.cs b/MediaBrowser.Server.Implementations/Library/CoreResolutionIgnoreRule.cs
index ba7e33890..b550d1dda 100644
--- a/MediaBrowser.Server.Implementations/Library/CoreResolutionIgnoreRule.cs
+++ b/MediaBrowser.Server.Implementations/Library/CoreResolutionIgnoreRule.cs
@@ -33,7 +33,8 @@ namespace MediaBrowser.Server.Implementations.Library
// Synology
"@eaDir",
- "eaDir"
+ "eaDir",
+ "#recycle"
};
diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs
index 442e2ebe5..b2302cf86 100644
--- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs
+++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs
@@ -43,6 +43,7 @@ using MediaBrowser.Server.Implementations.Library.Resolvers;
using MoreLinq;
using SortOrder = MediaBrowser.Model.Entities.SortOrder;
using VideoResolver = MediaBrowser.Naming.Video.VideoResolver;
+using MediaBrowser.Common.Configuration;
namespace MediaBrowser.Server.Implementations.Library
{
@@ -334,15 +335,6 @@ namespace MediaBrowser.Server.Implementations.Library
}
}
- /// <summary>
- /// Updates the item in library cache.
- /// </summary>
- /// <param name="item">The item.</param>
- private void UpdateItemInLibraryCache(BaseItem item)
- {
- RegisterItem(item);
- }
-
public void RegisterItem(BaseItem item)
{
if (item == null)
@@ -695,7 +687,7 @@ namespace MediaBrowser.Server.Implementations.Library
public IEnumerable<BaseItem> ResolvePaths(IEnumerable<FileSystemMetadata> files,
IDirectoryService directoryService,
- Folder parent,
+ Folder parent,
LibraryOptions libraryOptions,
string collectionType,
IItemResolver[] resolvers)
@@ -1216,12 +1208,7 @@ namespace MediaBrowser.Server.Implementations.Library
if (libraryFolder != null)
{
info.ItemId = libraryFolder.Id.ToString("N");
- }
-
- var collectionFolder = libraryFolder as CollectionFolder;
- if (collectionFolder != null)
- {
- info.LibraryOptions = collectionFolder.GetLibraryOptions();
+ info.LibraryOptions = GetLibraryOptions(libraryFolder);
}
return info;
@@ -1490,10 +1477,10 @@ namespace MediaBrowser.Server.Implementations.Library
private void AddUserToQuery(InternalItemsQuery query, User user)
{
- if (query.AncestorIds.Length == 0 &&
- !query.ParentId.HasValue &&
- query.ChannelIds.Length == 0 &&
- query.TopParentIds.Length == 0 &&
+ if (query.AncestorIds.Length == 0 &&
+ !query.ParentId.HasValue &&
+ query.ChannelIds.Length == 0 &&
+ query.TopParentIds.Length == 0 &&
string.IsNullOrWhiteSpace(query.AncestorWithPresentationUniqueKey)
&& query.ItemIds.Length == 0)
{
@@ -1781,7 +1768,7 @@ namespace MediaBrowser.Server.Implementations.Library
foreach (var item in list)
{
- UpdateItemInLibraryCache(item);
+ RegisterItem(item);
}
if (ItemAdded != null)
@@ -1822,7 +1809,7 @@ namespace MediaBrowser.Server.Implementations.Library
await ItemRepository.SaveItem(item, cancellationToken).ConfigureAwait(false);
- UpdateItemInLibraryCache(item);
+ RegisterItem(item);
if (ItemUpdated != null)
{
@@ -1889,11 +1876,41 @@ namespace MediaBrowser.Server.Implementations.Library
public LibraryOptions GetLibraryOptions(BaseItem item)
{
- var collectionFolder = GetCollectionFolders(item)
- .OfType<CollectionFolder>()
- .FirstOrDefault();
+ var collectionFolder = item as CollectionFolder;
+ if (collectionFolder == null)
+ {
+ collectionFolder = GetCollectionFolders(item)
+ .OfType<CollectionFolder>()
+ .FirstOrDefault();
+ }
+
+ var options = collectionFolder == null ? new LibraryOptions() : collectionFolder.GetLibraryOptions();
+
+ if (options.SchemaVersion < 3)
+ {
+ options.SaveLocalMetadata = ConfigurationManager.Configuration.SaveLocalMeta;
+ options.EnableInternetProviders = ConfigurationManager.Configuration.EnableInternetProviders;
+ }
+
+ if (options.SchemaVersion < 2)
+ {
+ var chapterOptions = ConfigurationManager.GetConfiguration<ChapterOptions>("chapters");
+ options.ExtractChapterImagesDuringLibraryScan = chapterOptions.ExtractDuringLibraryScan;
- return collectionFolder == null ? new LibraryOptions() : collectionFolder.GetLibraryOptions();
+ if (collectionFolder != null)
+ {
+ if (string.Equals(collectionFolder.CollectionType, "movies", StringComparison.OrdinalIgnoreCase))
+ {
+ options.EnableChapterImageExtraction = chapterOptions.EnableMovieChapterImageExtraction;
+ }
+ else if (string.Equals(collectionFolder.CollectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
+ {
+ options.EnableChapterImageExtraction = chapterOptions.EnableEpisodeChapterImageExtraction;
+ }
+ }
+ }
+
+ return options;
}
public string GetContentType(BaseItem item)
@@ -2437,7 +2454,7 @@ namespace MediaBrowser.Server.Implementations.Library
public IEnumerable<Video> FindTrailers(BaseItem owner, List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
{
- var files = owner.IsInMixedFolder ? new List<FileSystemMetadata>() : fileSystemChildren.Where(i => i.IsDirectory)
+ var files = owner.DetectIsInMixedFolder() ? new List<FileSystemMetadata>() : fileSystemChildren.Where(i => i.IsDirectory)
.Where(i => string.Equals(i.Name, BaseItem.TrailerFolderName, StringComparison.OrdinalIgnoreCase))
.SelectMany(i => _fileSystem.GetFiles(i.FullName, false))
.ToList();
@@ -2527,8 +2544,60 @@ namespace MediaBrowser.Server.Implementations.Library
}).OrderBy(i => i.Path).ToList();
}
+ public string GetPathAfterNetworkSubstitution(string path, BaseItem ownerItem)
+ {
+ if (ownerItem != null)
+ {
+ var libraryOptions = GetLibraryOptions(ownerItem);
+ if (libraryOptions != null)
+ {
+ foreach (var pathInfo in libraryOptions.PathInfos)
+ {
+ if (string.IsNullOrWhiteSpace(pathInfo.NetworkPath))
+ {
+ continue;
+ }
+
+ var substitutionResult = SubstitutePathInternal(path, pathInfo.Path, pathInfo.NetworkPath);
+ if (substitutionResult.Item2)
+ {
+ return substitutionResult.Item1;
+ }
+ }
+ }
+ }
+
+ var metadataPath = ConfigurationManager.Configuration.MetadataPath;
+ var metadataNetworkPath = ConfigurationManager.Configuration.MetadataNetworkPath;
+
+ if (!string.IsNullOrWhiteSpace(metadataPath) && !string.IsNullOrWhiteSpace(metadataNetworkPath))
+ {
+ var metadataSubstitutionResult = SubstitutePathInternal(path, metadataPath, metadataNetworkPath);
+ if (metadataSubstitutionResult.Item2)
+ {
+ return metadataSubstitutionResult.Item1;
+ }
+ }
+
+ foreach (var map in ConfigurationManager.Configuration.PathSubstitutions)
+ {
+ var substitutionResult = SubstitutePathInternal(path, map.From, map.To);
+ if (substitutionResult.Item2)
+ {
+ return substitutionResult.Item1;
+ }
+ }
+
+ return path;
+ }
+
public string SubstitutePath(string path, string from, string to)
{
+ return SubstitutePathInternal(path, from, to).Item1;
+ }
+
+ private Tuple<string, bool> SubstitutePathInternal(string path, string from, string to)
+ {
if (string.IsNullOrWhiteSpace(path))
{
throw new ArgumentNullException("path");
@@ -2542,7 +2611,11 @@ namespace MediaBrowser.Server.Implementations.Library
throw new ArgumentNullException("to");
}
+ from = from.Trim();
+ to = to.Trim();
+
var newPath = path.Replace(from, to, StringComparison.OrdinalIgnoreCase);
+ var changed = false;
if (!string.Equals(newPath, path))
{
@@ -2554,9 +2627,11 @@ namespace MediaBrowser.Server.Implementations.Library
{
newPath = newPath.Replace('/', '\\');
}
+
+ changed = true;
}
- return newPath;
+ return new Tuple<string, bool>(newPath, changed);
}
private void SetExtraTypeFromFilename(Video item)
@@ -2685,7 +2760,7 @@ namespace MediaBrowser.Server.Implementations.Library
throw new InvalidOperationException();
}
- public void AddVirtualFolder(string name, string collectionType, string[] mediaPaths, LibraryOptions options, bool refreshLibrary)
+ public void AddVirtualFolder(string name, string collectionType, LibraryOptions options, bool refreshLibrary)
{
if (string.IsNullOrWhiteSpace(name))
{
@@ -2703,12 +2778,13 @@ namespace MediaBrowser.Server.Implementations.Library
virtualFolderPath = Path.Combine(rootFolderPath, name);
}
- if (mediaPaths != null)
+ var mediaPathInfos = options.PathInfos;
+ if (mediaPathInfos != null)
{
- var invalidpath = mediaPaths.FirstOrDefault(i => !_fileSystem.DirectoryExists(i));
+ var invalidpath = mediaPathInfos.FirstOrDefault(i => !_fileSystem.DirectoryExists(i.Path));
if (invalidpath != null)
{
- throw new ArgumentException("The specified path does not exist: " + invalidpath + ".");
+ throw new ArgumentException("The specified path does not exist: " + invalidpath.Path + ".");
}
}
@@ -2730,11 +2806,11 @@ namespace MediaBrowser.Server.Implementations.Library
CollectionFolder.SaveLibraryOptions(virtualFolderPath, options);
- if (mediaPaths != null)
+ if (mediaPathInfos != null)
{
- foreach (var path in mediaPaths)
+ foreach (var path in mediaPathInfos)
{
- AddMediaPath(name, path);
+ AddMediaPathInternal(name, path, false);
}
}
}
@@ -2760,6 +2836,137 @@ namespace MediaBrowser.Server.Implementations.Library
}
}
+ private bool ValidateNetworkPath(string path)
+ {
+ if (Environment.OSVersion.Platform == PlatformID.Win32NT || !path.StartsWith("\\\\", StringComparison.OrdinalIgnoreCase))
+ {
+ return Directory.Exists(path);
+ }
+
+ // Without native support for unc, we cannot validate this when running under mono
+ return true;
+ }
+
+ private const string ShortcutFileExtension = ".mblink";
+ private const string ShortcutFileSearch = "*" + ShortcutFileExtension;
+ public void AddMediaPath(string virtualFolderName, MediaPathInfo pathInfo)
+ {
+ AddMediaPathInternal(virtualFolderName, pathInfo, true);
+ }
+
+ private void AddMediaPathInternal(string virtualFolderName, MediaPathInfo pathInfo, bool saveLibraryOptions)
+ {
+ if (pathInfo == null)
+ {
+ throw new ArgumentNullException("path");
+ }
+
+ var path = pathInfo.Path;
+
+ if (string.IsNullOrWhiteSpace(path))
+ {
+ throw new ArgumentNullException("path");
+ }
+
+ if (!_fileSystem.DirectoryExists(path))
+ {
+ throw new DirectoryNotFoundException("The path does not exist.");
+ }
+
+ if (!string.IsNullOrWhiteSpace(pathInfo.NetworkPath) && !ValidateNetworkPath(pathInfo.NetworkPath))
+ {
+ throw new DirectoryNotFoundException("The network path does not exist.");
+ }
+
+ var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
+ var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName);
+
+ var shortcutFilename = _fileSystem.GetFileNameWithoutExtension(path);
+
+ var lnk = Path.Combine(virtualFolderPath, shortcutFilename + ShortcutFileExtension);
+
+ while (_fileSystem.FileExists(lnk))
+ {
+ shortcutFilename += "1";
+ lnk = Path.Combine(virtualFolderPath, shortcutFilename + ShortcutFileExtension);
+ }
+
+ _fileSystem.CreateShortcut(lnk, path);
+
+ RemoveContentTypeOverrides(path);
+
+ if (saveLibraryOptions)
+ {
+ var libraryOptions = CollectionFolder.GetLibraryOptions(virtualFolderPath);
+
+ var list = libraryOptions.PathInfos.ToList();
+ list.Add(pathInfo);
+ libraryOptions.PathInfos = list.ToArray();
+
+ SyncLibraryOptionsToLocations(virtualFolderPath, libraryOptions);
+
+ CollectionFolder.SaveLibraryOptions(virtualFolderPath, libraryOptions);
+ }
+ }
+
+ public void UpdateMediaPath(string virtualFolderName, MediaPathInfo pathInfo)
+ {
+ if (pathInfo == null)
+ {
+ throw new ArgumentNullException("path");
+ }
+
+ if (!string.IsNullOrWhiteSpace(pathInfo.NetworkPath) && !ValidateNetworkPath(pathInfo.NetworkPath))
+ {
+ throw new DirectoryNotFoundException("The network path does not exist.");
+ }
+
+ var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
+ var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName);
+
+ var libraryOptions = CollectionFolder.GetLibraryOptions(virtualFolderPath);
+
+ SyncLibraryOptionsToLocations(virtualFolderPath, libraryOptions);
+
+ var list = libraryOptions.PathInfos.ToList();
+ foreach (var originalPathInfo in list)
+ {
+ if (string.Equals(pathInfo.Path, originalPathInfo.Path, StringComparison.Ordinal))
+ {
+ originalPathInfo.NetworkPath = pathInfo.NetworkPath;
+ break;
+ }
+ }
+
+ libraryOptions.PathInfos = list.ToArray();
+
+ CollectionFolder.SaveLibraryOptions(virtualFolderPath, libraryOptions);
+ }
+
+ private void SyncLibraryOptionsToLocations(string virtualFolderPath, LibraryOptions options)
+ {
+ var topLibraryFolders = GetUserRootFolder().Children.ToList();
+ var info = GetVirtualFolderInfo(virtualFolderPath, topLibraryFolders);
+
+ if (info.Locations.Count > 0 && info.Locations.Count != options.PathInfos.Length)
+ {
+ var list = options.PathInfos.ToList();
+
+ foreach (var location in info.Locations)
+ {
+ if (!list.Any(i => string.Equals(i.Path, location, StringComparison.Ordinal)))
+ {
+ list.Add(new MediaPathInfo
+ {
+ Path = location
+ });
+ }
+ }
+
+ options.PathInfos = list.ToArray();
+ }
+ }
+
public void RemoveVirtualFolder(string name, bool refreshLibrary)
{
if (string.IsNullOrWhiteSpace(name))
@@ -2804,38 +3011,6 @@ namespace MediaBrowser.Server.Implementations.Library
}
}
- private const string ShortcutFileExtension = ".mblink";
- private const string ShortcutFileSearch = "*" + ShortcutFileExtension;
- public void AddMediaPath(string virtualFolderName, string path)
- {
- if (string.IsNullOrWhiteSpace(path))
- {
- throw new ArgumentNullException("path");
- }
-
- if (!_fileSystem.DirectoryExists(path))
- {
- throw new DirectoryNotFoundException("The path does not exist.");
- }
-
- var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
- var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName);
-
- var shortcutFilename = _fileSystem.GetFileNameWithoutExtension(path);
-
- var lnk = Path.Combine(virtualFolderPath, shortcutFilename + ShortcutFileExtension);
-
- while (_fileSystem.FileExists(lnk))
- {
- shortcutFilename += "1";
- lnk = Path.Combine(virtualFolderPath, shortcutFilename + ShortcutFileExtension);
- }
-
- _fileSystem.CreateShortcut(lnk, path);
-
- RemoveContentTypeOverrides(path);
- }
-
private void RemoveContentTypeOverrides(string path)
{
if (string.IsNullOrWhiteSpace(path))
@@ -2872,19 +3047,28 @@ namespace MediaBrowser.Server.Implementations.Library
}
var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
- var path = Path.Combine(rootFolderPath, virtualFolderName);
+ var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName);
- if (!_fileSystem.DirectoryExists(path))
+ if (!_fileSystem.DirectoryExists(virtualFolderPath))
{
throw new DirectoryNotFoundException(string.Format("The media collection {0} does not exist", virtualFolderName));
}
- var shortcut = Directory.EnumerateFiles(path, ShortcutFileSearch, SearchOption.AllDirectories).FirstOrDefault(f => _fileSystem.ResolveShortcut(f).Equals(mediaPath, StringComparison.OrdinalIgnoreCase));
+ var shortcut = Directory.EnumerateFiles(virtualFolderPath, ShortcutFileSearch, SearchOption.AllDirectories).FirstOrDefault(f => _fileSystem.ResolveShortcut(f).Equals(mediaPath, StringComparison.OrdinalIgnoreCase));
if (!string.IsNullOrEmpty(shortcut))
{
_fileSystem.DeleteFile(shortcut);
}
+
+ var libraryOptions = CollectionFolder.GetLibraryOptions(virtualFolderPath);
+
+ libraryOptions.PathInfos = libraryOptions
+ .PathInfos
+ .Where(i => !string.Equals(i.Path, mediaPath, StringComparison.Ordinal))
+ .ToArray();
+
+ CollectionFolder.SaveLibraryOptions(virtualFolderPath, libraryOptions);
}
}
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs
index 4a533ff93..e7bfe56f2 100644
--- a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs
+++ b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs
@@ -221,8 +221,28 @@ namespace MediaBrowser.Server.Implementations.Library
}
}
- public async Task<MediaSourceInfo> GetMediaSource(IHasMediaSources item, string mediaSourceId, bool enablePathSubstitution)
+ public async Task<MediaSourceInfo> GetMediaSource(IHasMediaSources item, string mediaSourceId, string liveStreamId, bool enablePathSubstitution, CancellationToken cancellationToken)
{
+ if (!string.IsNullOrWhiteSpace(liveStreamId))
+ {
+ return await GetLiveStream(liveStreamId, cancellationToken).ConfigureAwait(false);
+ }
+ //await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
+
+ //try
+ //{
+ // var stream = _openStreams.Values.FirstOrDefault(i => string.Equals(i.MediaSource.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase));
+
+ // if (stream != null)
+ // {
+ // return stream.MediaSource;
+ // }
+ //}
+ //finally
+ //{
+ // _liveStreamSemaphore.Release();
+ //}
+
var sources = await GetPlayackMediaSources(item.Id.ToString("N"), null, enablePathSubstitution, new[] { MediaType.Audio, MediaType.Video },
CancellationToken.None).ConfigureAwait(false);
@@ -335,7 +355,7 @@ namespace MediaBrowser.Server.Implementations.Library
.ToList();
}
- private readonly ConcurrentDictionary<string, LiveStreamInfo> _openStreams = new ConcurrentDictionary<string, LiveStreamInfo>(StringComparer.OrdinalIgnoreCase);
+ private readonly Dictionary<string, LiveStreamInfo> _openStreams = new Dictionary<string, LiveStreamInfo>(StringComparer.OrdinalIgnoreCase);
private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1);
public async Task<LiveStreamResponse> OpenLiveStream(LiveStreamRequest request, bool enableAutoClose, CancellationToken cancellationToken)
@@ -347,7 +367,9 @@ namespace MediaBrowser.Server.Implementations.Library
var tuple = GetProvider(request.OpenToken);
var provider = tuple.Item1;
- var mediaSource = await provider.OpenMediaSource(tuple.Item2, cancellationToken).ConfigureAwait(false);
+ var mediaSourceTuple = await provider.OpenMediaSource(tuple.Item2, cancellationToken).ConfigureAwait(false);
+
+ var mediaSource = mediaSourceTuple.Item1;
if (string.IsNullOrWhiteSpace(mediaSource.LiveStreamId))
{
@@ -361,9 +383,11 @@ namespace MediaBrowser.Server.Implementations.Library
Date = DateTime.UtcNow,
EnableCloseTimer = enableAutoClose,
Id = mediaSource.LiveStreamId,
- MediaSource = mediaSource
+ MediaSource = mediaSource,
+ DirectStreamProvider = mediaSourceTuple.Item2
};
- _openStreams.AddOrUpdate(mediaSource.LiveStreamId, info, (key, i) => info);
+
+ _openStreams[mediaSource.LiveStreamId] = info;
if (enableAutoClose)
{
@@ -394,14 +418,14 @@ namespace MediaBrowser.Server.Implementations.Library
}
}
- public async Task<MediaSourceInfo> GetLiveStream(string id, CancellationToken cancellationToken)
+ public async Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> GetLiveStreamWithDirectStreamProvider(string id, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(id))
{
throw new ArgumentNullException("id");
}
- _logger.Debug("Getting live stream {0}", id);
+ _logger.Debug("Getting already opened live stream {0}", id);
await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
@@ -410,7 +434,7 @@ namespace MediaBrowser.Server.Implementations.Library
LiveStreamInfo info;
if (_openStreams.TryGetValue(id, out info))
{
- return info.MediaSource;
+ return new Tuple<MediaSourceInfo, IDirectStreamProvider>(info.MediaSource, info.DirectStreamProvider);
}
else
{
@@ -423,6 +447,12 @@ namespace MediaBrowser.Server.Implementations.Library
}
}
+ public async Task<MediaSourceInfo> GetLiveStream(string id, CancellationToken cancellationToken)
+ {
+ var result = await GetLiveStreamWithDirectStreamProvider(id, cancellationToken).ConfigureAwait(false);
+ return result.Item1;
+ }
+
public async Task PingLiveStream(string id, CancellationToken cancellationToken)
{
await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
@@ -436,7 +466,7 @@ namespace MediaBrowser.Server.Implementations.Library
}
else
{
- _logger.Error("Failed to update MediaSource timestamp for {0}", id);
+ _logger.Error("Failed to ping live stream {0}", id);
}
}
finally
@@ -445,17 +475,16 @@ namespace MediaBrowser.Server.Implementations.Library
}
}
- private async Task CloseLiveStreamWithProvider(IMediaSourceProvider provider, string streamId, CancellationToken cancellationToken)
+ private async Task CloseLiveStreamWithProvider(IMediaSourceProvider provider, string streamId)
{
_logger.Info("Closing live stream {0} with provider {1}", streamId, provider.GetType().Name);
try
{
- await provider.CloseMediaSource(streamId, cancellationToken).ConfigureAwait(false);
+ await provider.CloseMediaSource(streamId).ConfigureAwait(false);
}
catch (NotImplementedException)
{
-
}
catch (Exception ex)
{
@@ -463,37 +492,35 @@ namespace MediaBrowser.Server.Implementations.Library
}
}
- public async Task CloseLiveStream(string id, CancellationToken cancellationToken)
+ public async Task CloseLiveStream(string id)
{
if (string.IsNullOrWhiteSpace(id))
{
throw new ArgumentNullException("id");
}
- await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
+ await _liveStreamSemaphore.WaitAsync().ConfigureAwait(false);
try
{
LiveStreamInfo current;
+
if (_openStreams.TryGetValue(id, out current))
{
+ _openStreams.Remove(id);
+ current.Closed = true;
+
if (current.MediaSource.RequiresClosing)
{
var tuple = GetProvider(id);
- await CloseLiveStreamWithProvider(tuple.Item1, tuple.Item2, cancellationToken).ConfigureAwait(false);
+ await CloseLiveStreamWithProvider(tuple.Item1, tuple.Item2).ConfigureAwait(false);
}
- }
- LiveStreamInfo removed;
- if (_openStreams.TryRemove(id, out removed))
- {
- removed.Closed = true;
- }
-
- if (_openStreams.Count == 0)
- {
- StopCloseTimer();
+ if (_openStreams.Count == 0)
+ {
+ StopCloseTimer();
+ }
}
}
finally
@@ -523,7 +550,7 @@ namespace MediaBrowser.Server.Implementations.Library
}
private Timer _closeTimer;
- private readonly TimeSpan _openStreamMaxAge = TimeSpan.FromSeconds(60);
+ private readonly TimeSpan _openStreamMaxAge = TimeSpan.FromSeconds(180);
private void StartCloseTimer()
{
@@ -545,10 +572,20 @@ namespace MediaBrowser.Server.Implementations.Library
private async void CloseTimerCallback(object state)
{
- var infos = _openStreams
- .Values
- .Where(i => i.EnableCloseTimer && DateTime.UtcNow - i.Date > _openStreamMaxAge)
- .ToList();
+ List<LiveStreamInfo> infos;
+ await _liveStreamSemaphore.WaitAsync().ConfigureAwait(false);
+
+ try
+ {
+ infos = _openStreams
+ .Values
+ .Where(i => i.EnableCloseTimer && DateTime.UtcNow - i.Date > _openStreamMaxAge)
+ .ToList();
+ }
+ finally
+ {
+ _liveStreamSemaphore.Release();
+ }
foreach (var info in infos)
{
@@ -556,7 +593,7 @@ namespace MediaBrowser.Server.Implementations.Library
{
try
{
- await CloseLiveStream(info.Id, CancellationToken.None).ConfigureAwait(false);
+ await CloseLiveStream(info.Id).ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -588,12 +625,10 @@ namespace MediaBrowser.Server.Implementations.Library
{
foreach (var key in _openStreams.Keys.ToList())
{
- var task = CloseLiveStream(key, CancellationToken.None);
+ var task = CloseLiveStream(key);
Task.WaitAll(task);
}
-
- _openStreams.Clear();
}
}
}
@@ -605,6 +640,7 @@ namespace MediaBrowser.Server.Implementations.Library
public string Id;
public bool Closed;
public MediaSourceInfo MediaSource;
+ public IDirectStreamProvider DirectStreamProvider;
}
}
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs
index 1a8295800..7f35fc3ea 100644
--- a/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs
+++ b/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs
@@ -54,8 +54,8 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio
if (!args.IsDirectory) return null;
// Avoid mis-identifying top folders
- if (args.Parent.IsRoot) return null;
if (args.HasParent<MusicAlbum>()) return null;
+ if (args.Parent.IsRoot) return null;
var collectionType = args.GetCollectionType();
diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs
index e819af06f..686105ddb 100644
--- a/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs
+++ b/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs
@@ -7,6 +7,7 @@ using System;
using System.IO;
using System.Linq;
using CommonIO;
+using MediaBrowser.Controller.Configuration;
namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio
{
@@ -18,12 +19,14 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio
private readonly ILogger _logger;
private readonly IFileSystem _fileSystem;
private readonly ILibraryManager _libraryManager;
+ private readonly IServerConfigurationManager _config;
- public MusicArtistResolver(ILogger logger, IFileSystem fileSystem, ILibraryManager libraryManager)
+ public MusicArtistResolver(ILogger logger, IFileSystem fileSystem, ILibraryManager libraryManager, IServerConfigurationManager config)
{
_logger = logger;
_fileSystem = fileSystem;
_libraryManager = libraryManager;
+ _config = config;
}
/// <summary>
@@ -48,9 +51,6 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio
{
if (!args.IsDirectory) return null;
- // Avoid mis-identifying top folders
- if (args.Parent.IsRoot) return null;
-
// Don't allow nested artists
if (args.HasParent<MusicArtist>() || args.HasParent<MusicAlbum>())
{
@@ -67,6 +67,19 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio
return null;
}
+ if (args.ContainsFileSystemEntryByName("artist.nfo"))
+ {
+ return new MusicArtist();
+ }
+
+ if (_config.Configuration.EnableSimpleArtistDetection)
+ {
+ return null;
+ }
+
+ // Avoid mis-identifying top folders
+ if (args.Parent.IsRoot) return null;
+
var directoryService = args.DirectoryService;
var albumResolver = new MusicAlbumResolver(_logger, _fileSystem, _libraryManager);
diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/FolderResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/FolderResolver.cs
index 34237622d..ff07c5282 100644
--- a/MediaBrowser.Server.Implementations/Library/Resolvers/FolderResolver.cs
+++ b/MediaBrowser.Server.Implementations/Library/Resolvers/FolderResolver.cs
@@ -51,7 +51,6 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers
base.SetInitialItemValues(item, args);
item.IsRoot = args.Parent == null;
- item.IsPhysicalRoot = args.IsPhysicalRoot;
}
}
}
diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
index ee9533d2a..c3d5f3441 100644
--- a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
+++ b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
@@ -48,12 +48,6 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
string collectionType,
IDirectoryService directoryService)
{
- if (parent != null && parent.Path != null && parent.Path.IndexOf("disney", StringComparison.OrdinalIgnoreCase) != -1)
- {
- var b = true;
- var a = b;
- }
-
var result = ResolveMultipleInternal(parent, files, collectionType, directoryService);
if (result != null)
@@ -213,26 +207,22 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
// Find movies with their own folders
if (args.IsDirectory)
{
- var files = args.FileSystemChildren
- .Where(i => !LibraryManager.IgnoreFile(i, args.Parent))
- .ToList();
-
if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase))
{
- return FindMovie<MusicVideo>(args.Path, args.Parent, files, args.DirectoryService, collectionType);
+ return null;
}
if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase))
{
- return FindMovie<Video>(args.Path, args.Parent, files, args.DirectoryService, collectionType);
+ return null;
}
if (string.IsNullOrEmpty(collectionType))
{
- // Owned items should just use the plain video type
+ // Owned items will be caught by the plain video resolver
if (args.Parent == null)
{
- return FindMovie<Video>(args.Path, args.Parent, files, args.DirectoryService, collectionType);
+ return null;
}
if (args.HasParent<Series>())
@@ -240,11 +230,21 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
return null;
}
- return FindMovie<Movie>(args.Path, args.Parent, files, args.DirectoryService, collectionType);
+ {
+ var files = args.FileSystemChildren
+ .Where(i => !LibraryManager.IgnoreFile(i, args.Parent))
+ .ToList();
+
+ return FindMovie<Movie>(args.Path, args.Parent, files, args.DirectoryService, collectionType);
+ }
}
if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase))
{
+ var files = args.FileSystemChildren
+ .Where(i => !LibraryManager.IgnoreFile(i, args.Parent))
+ .ToList();
+
return FindMovie<Movie>(args.Path, args.Parent, files, args.DirectoryService, collectionType);
}
diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs
index 4c6254330..c82825007 100644
--- a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs
+++ b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs
@@ -54,11 +54,20 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV
{
if (args.IsDirectory)
{
- if (args.HasParent<Series>())
+ if (args.HasParent<Series>() || args.HasParent<Season>())
{
return null;
}
+ if (args.ContainsFileSystemEntryByName("tvshow.nfo"))
+ {
+ return new Series
+ {
+ Path = args.Path,
+ Name = Path.GetFileName(args.Path)
+ };
+ }
+
var collectionType = args.GetCollectionType();
if (string.Equals(collectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
{
@@ -72,22 +81,20 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV
};
}
}
- else
+ else if (string.IsNullOrWhiteSpace(collectionType))
{
- if (string.IsNullOrWhiteSpace(collectionType))
+ if (args.Parent.IsRoot)
{
- if (args.Parent.IsRoot)
- {
- return null;
- }
- if (IsSeriesFolder(args.Path, args.FileSystemChildren, args.DirectoryService, _fileSystem, _logger, _libraryManager, args.GetLibraryOptions(), false))
+ return null;
+ }
+
+ if (IsSeriesFolder(args.Path, args.FileSystemChildren, args.DirectoryService, _fileSystem, _logger, _libraryManager, args.GetLibraryOptions(), false))
+ {
+ return new Series
{
- return new Series
- {
- Path = args.Path,
- Name = Path.GetFileName(args.Path)
- };
- }
+ Path = args.Path,
+ Name = Path.GetFileName(args.Path)
+ };
}
}
}
diff --git a/MediaBrowser.Server.Implementations/Library/SearchEngine.cs b/MediaBrowser.Server.Implementations/Library/SearchEngine.cs
index a6d6b5cb8..40cca7bab 100644
--- a/MediaBrowser.Server.Implementations/Library/SearchEngine.cs
+++ b/MediaBrowser.Server.Implementations/Library/SearchEngine.cs
@@ -105,6 +105,7 @@ namespace MediaBrowser.Server.Implementations.Library
var includeItemTypes = (query.IncludeItemTypes ?? new string[] { }).ToList();
excludeItemTypes.Add(typeof(Year).Name);
+ excludeItemTypes.Add(typeof(Folder).Name);
if (query.IncludeGenres && (includeItemTypes.Count == 0 || includeItemTypes.Contains("Genre", StringComparer.OrdinalIgnoreCase)))
{
diff --git a/MediaBrowser.Server.Implementations/Library/UserDataManager.cs b/MediaBrowser.Server.Implementations/Library/UserDataManager.cs
index 307cf4cd2..9ee65a57c 100644
--- a/MediaBrowser.Server.Implementations/Library/UserDataManager.cs
+++ b/MediaBrowser.Server.Implementations/Library/UserDataManager.cs
@@ -61,16 +61,7 @@ namespace MediaBrowser.Server.Implementations.Library
foreach (var key in keys)
{
- try
- {
- await Repository.SaveUserData(userId, key, userData, cancellationToken).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error saving user data", ex);
-
- throw;
- }
+ await Repository.SaveUserData(userId, key, userData, cancellationToken).ConfigureAwait(false);
}
var cacheKey = GetCacheKey(userId, item.Id);
@@ -107,18 +98,7 @@ namespace MediaBrowser.Server.Implementations.Library
cancellationToken.ThrowIfCancellationRequested();
- try
- {
- await Repository.SaveAllUserData(userId, userData, cancellationToken).ConfigureAwait(false);
-
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error saving user data", ex);
-
- throw;
- }
-
+ await Repository.SaveAllUserData(userId, userData, cancellationToken).ConfigureAwait(false);
}
/// <summary>
@@ -289,6 +269,11 @@ namespace MediaBrowser.Server.Implementations.Library
positionTicks = 0;
}
+ if (!item.SupportsPlayedStatus)
+ {
+ positionTicks = 0;
+ data.Played = false;
+ }
if (item is Audio)
{
positionTicks = 0;
diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
index b21aa904b..0f8c15e71 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
@@ -47,25 +47,60 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
_logger.Info("Copying recording stream to file {0}", targetFile);
- if (mediaSource.RunTimeTicks.HasValue)
- {
- // The media source already has a fixed duration
- // But add another stop 1 minute later just in case the recording gets stuck for any reason
- var durationToken = new CancellationTokenSource(duration.Add(TimeSpan.FromMinutes(1)));
- cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
- }
- else
- {
- // The media source if infinite so we need to handle stopping ourselves
- var durationToken = new CancellationTokenSource(duration);
- cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
- }
-
- await response.Content.CopyToAsync(output, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false);
+ // The media source if infinite so we need to handle stopping ourselves
+ var durationToken = new CancellationTokenSource(duration);
+ cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
+
+ await CopyUntilCancelled(response.Content, output, cancellationToken).ConfigureAwait(false);
}
}
_logger.Info("Recording completed to file {0}", targetFile);
}
+
+ private const int BufferSize = 81920;
+ public static Task CopyUntilCancelled(Stream source, Stream target, CancellationToken cancellationToken)
+ {
+ return CopyUntilCancelled(source, target, null, cancellationToken);
+ }
+ public static async Task CopyUntilCancelled(Stream source, Stream target, Action onStarted, CancellationToken cancellationToken)
+ {
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ var bytesRead = await CopyToAsyncInternal(source, target, BufferSize, onStarted, cancellationToken).ConfigureAwait(false);
+
+ onStarted = null;
+
+ //var position = fs.Position;
+ //_logger.Debug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path);
+
+ if (bytesRead == 0)
+ {
+ await Task.Delay(100).ConfigureAwait(false);
+ }
+ }
+ }
+
+ private static async Task<int> CopyToAsyncInternal(Stream source, Stream destination, Int32 bufferSize, Action onStarted, CancellationToken cancellationToken)
+ {
+ byte[] buffer = new byte[bufferSize];
+ int bytesRead;
+ int totalBytesRead = 0;
+
+ while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0)
+ {
+ await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false);
+
+ totalBytesRead += bytesRead;
+
+ if (onStarted != null)
+ {
+ onStarted();
+ }
+ onStarted = null;
+ }
+
+ return totalBytesRead;
+ }
}
}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
index 8fa34109d..136a9e195 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
@@ -22,20 +22,25 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
+using System.Text;
using System.Threading;
using System.Threading.Tasks;
+using System.Xml;
using CommonIO;
+using MediaBrowser.Common.Events;
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Controller;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
-using MediaBrowser.Controller.Power;
using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.FileOrganization;
using Microsoft.Win32;
namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{
- public class EmbyTV : ILiveTvService, ISupportsNewTimerIds, IDisposable
+ public class EmbyTV : ILiveTvService, ISupportsDirectStreamProvider, ISupportsNewTimerIds, IDisposable
{
- private readonly IApplicationHost _appHpst;
+ private readonly IServerApplicationHost _appHost;
private readonly ILogger _logger;
private readonly IHttpClient _httpClient;
private readonly IServerConfigurationManager _config;
@@ -55,17 +60,17 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
public static EmbyTV Current;
- public event EventHandler DataSourceChanged { add { } remove { } }
- public event EventHandler<RecordingStatusChangedEventArgs> RecordingStatusChanged { add { } remove { } }
+ public event EventHandler DataSourceChanged;
+ public event EventHandler<RecordingStatusChangedEventArgs> RecordingStatusChanged;
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, ILibraryManager libraryManager, ILibraryMonitor libraryMonitor, IProviderManager providerManager, IFileOrganizationService organizationService, IMediaEncoder mediaEncoder, IPowerManagement powerManagement)
+ public EmbyTV(IServerApplicationHost appHost, ILogger logger, IJsonSerializer jsonSerializer, IHttpClient httpClient, IServerConfigurationManager config, ILiveTvManager liveTvManager, IFileSystem fileSystem, ILibraryManager libraryManager, ILibraryMonitor libraryMonitor, IProviderManager providerManager, IFileOrganizationService organizationService, IMediaEncoder mediaEncoder)
{
Current = this;
- _appHpst = appHost;
+ _appHost = appHost;
_logger = logger;
_httpClient = httpClient;
_config = config;
@@ -79,7 +84,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
_jsonSerializer = jsonSerializer;
_seriesTimerProvider = new SeriesTimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "seriestimers"));
- _timerProvider = new TimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "timers"), powerManagement, _logger);
+ _timerProvider = new TimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "timers"), _logger);
_timerProvider.TimerFired += _timerProvider_TimerFired;
_config.NamedConfigurationUpdated += _config_NamedConfigurationUpdated;
@@ -140,9 +145,15 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
continue;
}
+ var mediaPathInfos = pathsToCreate.Select(i => new MediaPathInfo { Path = i }).ToArray();
+
+ var libraryOptions = new LibraryOptions
+ {
+ PathInfos = mediaPathInfos
+ };
try
{
- _libraryManager.AddVirtualFolder(recordingFolder.Name, recordingFolder.CollectionType, pathsToCreate.ToArray(), new LibraryOptions(), true);
+ _libraryManager.AddVirtualFolder(recordingFolder.Name, recordingFolder.CollectionType, libraryOptions, true);
}
catch (Exception ex)
{
@@ -284,7 +295,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
status.Tuners = list;
status.Status = LiveTvServiceStatus.Ok;
- status.Version = _appHpst.ApplicationVersion.ToString();
+ status.Version = _appHost.ApplicationVersion.ToString();
status.IsVisible = false;
return status;
}
@@ -321,27 +332,25 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{
if (DateTime.UtcNow > timer.EndDate && !_activeRecordings.ContainsKey(timer.Id))
{
- _timerProvider.Delete(timer);
+ OnTimerOutOfDate(timer);
}
}
}
- private List<ChannelInfo> _channelCache = null;
- private async Task<IEnumerable<ChannelInfo>> GetChannelsAsync(bool enableCache, CancellationToken cancellationToken)
+ private void OnTimerOutOfDate(TimerInfo timer)
{
- if (enableCache && _channelCache != null)
- {
-
- return _channelCache.ToList();
- }
+ _timerProvider.Delete(timer);
+ }
+ private async Task<IEnumerable<ChannelInfo>> GetChannelsAsync(bool enableCache, CancellationToken cancellationToken)
+ {
var list = new List<ChannelInfo>();
foreach (var hostInstance in _liveTvManager.TunerHosts)
{
try
{
- var channels = await hostInstance.GetChannels(cancellationToken).ConfigureAwait(false);
+ var channels = await hostInstance.GetChannels(enableCache, cancellationToken).ConfigureAwait(false);
list.AddRange(channels);
}
@@ -374,7 +383,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
}
}
- _channelCache = list.ToList();
return list;
}
@@ -386,7 +394,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{
try
{
- var channels = await hostInstance.GetChannels(cancellationToken).ConfigureAwait(false);
+ var channels = await hostInstance.GetChannels(false, cancellationToken).ConfigureAwait(false);
list.AddRange(channels);
}
@@ -415,7 +423,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
foreach (var timer in timers)
{
- CancelTimerInternal(timer.Id);
+ CancelTimerInternal(timer.Id, true);
}
var remove = _seriesTimerProvider.GetAll().FirstOrDefault(r => string.Equals(r.Id, timerId, StringComparison.OrdinalIgnoreCase));
@@ -426,12 +434,20 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
return Task.FromResult(true);
}
- private void CancelTimerInternal(string timerId)
+ private void CancelTimerInternal(string timerId, bool isSeriesCancelled)
{
- var remove = _timerProvider.GetAll().FirstOrDefault(r => string.Equals(r.Id, timerId, StringComparison.OrdinalIgnoreCase));
- if (remove != null)
+ var timer = _timerProvider.GetTimer(timerId);
+ if (timer != null)
{
- _timerProvider.Delete(remove);
+ if (string.IsNullOrWhiteSpace(timer.SeriesTimerId) || isSeriesCancelled)
+ {
+ _timerProvider.Delete(timer);
+ }
+ else
+ {
+ timer.Status = RecordingStatus.Cancelled;
+ _timerProvider.AddOrUpdate(timer, false);
+ }
}
ActiveRecordingInfo activeRecordingInfo;
@@ -443,7 +459,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
public Task CancelTimerAsync(string timerId, CancellationToken cancellationToken)
{
- CancelTimerInternal(timerId);
+ CancelTimerInternal(timerId, false);
return Task.FromResult(true);
}
@@ -452,21 +468,57 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
return Task.FromResult(true);
}
- public Task CreateTimerAsync(TimerInfo info, CancellationToken cancellationToken)
+ public Task CreateSeriesTimerAsync(SeriesTimerInfo info, CancellationToken cancellationToken)
{
- return CreateTimer(info, cancellationToken);
+ throw new NotImplementedException();
}
- public Task CreateSeriesTimerAsync(SeriesTimerInfo info, CancellationToken cancellationToken)
+ public Task CreateTimerAsync(TimerInfo info, CancellationToken cancellationToken)
{
- return CreateSeriesTimer(info, cancellationToken);
+ throw new NotImplementedException();
}
- public Task<string> CreateTimer(TimerInfo info, CancellationToken cancellationToken)
+ public Task<string> CreateTimer(TimerInfo timer, CancellationToken cancellationToken)
{
- info.Id = Guid.NewGuid().ToString("N");
- _timerProvider.Add(info);
- return Task.FromResult(info.Id);
+ var existingTimer = _timerProvider.GetAll()
+ .FirstOrDefault(i => string.Equals(timer.ProgramId, i.ProgramId, StringComparison.OrdinalIgnoreCase));
+
+ if (existingTimer != null)
+ {
+ if (existingTimer.Status == RecordingStatus.Cancelled ||
+ existingTimer.Status == RecordingStatus.Completed)
+ {
+ existingTimer.Status = RecordingStatus.New;
+ _timerProvider.Update(existingTimer);
+ return Task.FromResult(existingTimer.Id);
+ }
+ else
+ {
+ throw new ArgumentException("A scheduled recording already exists for this program.");
+ }
+ }
+
+ timer.Id = Guid.NewGuid().ToString("N");
+
+ ProgramInfo programInfo = null;
+
+ if (!string.IsNullOrWhiteSpace(timer.ProgramId))
+ {
+ programInfo = GetProgramInfoFromCache(timer.ChannelId, timer.ProgramId);
+ }
+ if (programInfo == null)
+ {
+ _logger.Info("Unable to find program with Id {0}. Will search using start date", timer.ProgramId);
+ programInfo = GetProgramInfoFromCache(timer.ChannelId, timer.StartDate);
+ }
+
+ if (programInfo != null)
+ {
+ RecordingHelper.CopyProgramInfoToTimerInfo(programInfo, timer);
+ }
+
+ _timerProvider.Add(timer);
+ return Task.FromResult(timer.Id);
}
public async Task<string> CreateSeriesTimer(SeriesTimerInfo info, CancellationToken cancellationToken)
@@ -520,6 +572,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
instance.RecordAnyChannel = info.RecordAnyChannel;
instance.RecordAnyTime = info.RecordAnyTime;
instance.RecordNewOnly = info.RecordNewOnly;
+ instance.SkipEpisodesInLibrary = info.SkipEpisodesInLibrary;
+ instance.KeepUpTo = info.KeepUpTo;
+ instance.KeepUntil = info.KeepUntil;
instance.StartDate = info.StartDate;
_seriesTimerProvider.Update(instance);
@@ -540,12 +595,55 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
}
}
- public Task UpdateTimerAsync(TimerInfo info, CancellationToken cancellationToken)
+ public Task UpdateTimerAsync(TimerInfo updatedTimer, CancellationToken cancellationToken)
{
- _timerProvider.Update(info);
+ var existingTimer = _timerProvider.GetTimer(updatedTimer.Id);
+
+ if (existingTimer == null)
+ {
+ throw new ResourceNotFoundException();
+ }
+
+ // Only update if not currently active
+ ActiveRecordingInfo activeRecordingInfo;
+ if (!_activeRecordings.TryGetValue(updatedTimer.Id, out activeRecordingInfo))
+ {
+ existingTimer.PrePaddingSeconds = updatedTimer.PrePaddingSeconds;
+ existingTimer.PostPaddingSeconds = updatedTimer.PostPaddingSeconds;
+ existingTimer.IsPostPaddingRequired = updatedTimer.IsPostPaddingRequired;
+ existingTimer.IsPrePaddingRequired = updatedTimer.IsPrePaddingRequired;
+ }
+
return Task.FromResult(true);
}
+ private void UpdateExistingTimerWithNewMetadata(TimerInfo existingTimer, TimerInfo updatedTimer)
+ {
+ // Update the program info but retain the status
+ existingTimer.ChannelId = updatedTimer.ChannelId;
+ existingTimer.CommunityRating = updatedTimer.CommunityRating;
+ existingTimer.EndDate = updatedTimer.EndDate;
+ existingTimer.EpisodeNumber = updatedTimer.EpisodeNumber;
+ existingTimer.EpisodeTitle = updatedTimer.EpisodeTitle;
+ existingTimer.Genres = updatedTimer.Genres;
+ existingTimer.HomePageUrl = updatedTimer.HomePageUrl;
+ existingTimer.IsKids = updatedTimer.IsKids;
+ existingTimer.IsNews = updatedTimer.IsNews;
+ existingTimer.IsMovie = updatedTimer.IsMovie;
+ existingTimer.IsProgramSeries = updatedTimer.IsProgramSeries;
+ existingTimer.IsRepeat = updatedTimer.IsRepeat;
+ existingTimer.IsSports = updatedTimer.IsSports;
+ existingTimer.Name = updatedTimer.Name;
+ existingTimer.OfficialRating = updatedTimer.OfficialRating;
+ existingTimer.OriginalAirDate = updatedTimer.OriginalAirDate;
+ existingTimer.Overview = updatedTimer.Overview;
+ existingTimer.ProductionYear = updatedTimer.ProductionYear;
+ existingTimer.ProgramId = updatedTimer.ProgramId;
+ existingTimer.SeasonNumber = updatedTimer.SeasonNumber;
+ existingTimer.ShortOverview = updatedTimer.ShortOverview;
+ existingTimer.StartDate = updatedTimer.StartDate;
+ }
+
public Task<ImageStream> GetChannelImageAsync(string channelId, CancellationToken cancellationToken)
{
throw new NotImplementedException();
@@ -563,12 +661,76 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
public async Task<IEnumerable<RecordingInfo>> GetRecordingsAsync(CancellationToken cancellationToken)
{
- return new List<RecordingInfo>();
+ return _activeRecordings.Values.ToList().Select(GetRecordingInfo).ToList();
+ }
+
+ public string GetActiveRecordingPath(string id)
+ {
+ ActiveRecordingInfo info;
+
+ if (_activeRecordings.TryGetValue(id, out info))
+ {
+ return info.Path;
+ }
+ return null;
+ }
+
+ private RecordingInfo GetRecordingInfo(ActiveRecordingInfo info)
+ {
+ var timer = info.Timer;
+ var program = info.Program;
+
+ var result = new RecordingInfo
+ {
+ ChannelId = timer.ChannelId,
+ CommunityRating = timer.CommunityRating,
+ DateLastUpdated = DateTime.UtcNow,
+ EndDate = timer.EndDate,
+ EpisodeTitle = timer.EpisodeTitle,
+ Genres = timer.Genres,
+ Id = "recording" + timer.Id,
+ IsKids = timer.IsKids,
+ IsMovie = timer.IsMovie,
+ IsNews = timer.IsNews,
+ IsRepeat = timer.IsRepeat,
+ IsSeries = timer.IsProgramSeries,
+ IsSports = timer.IsSports,
+ Name = timer.Name,
+ OfficialRating = timer.OfficialRating,
+ OriginalAirDate = timer.OriginalAirDate,
+ Overview = timer.Overview,
+ ProgramId = timer.ProgramId,
+ SeriesTimerId = timer.SeriesTimerId,
+ StartDate = timer.StartDate,
+ Status = RecordingStatus.InProgress,
+ TimerId = timer.Id
+ };
+
+ if (program != null)
+ {
+ result.Audio = program.Audio;
+ result.ImagePath = program.ImagePath;
+ result.ImageUrl = program.ImageUrl;
+ result.IsHD = program.IsHD;
+ result.IsLive = program.IsLive;
+ result.IsPremiere = program.IsPremiere;
+ result.ShowId = program.ShowId;
+ }
+
+ return result;
}
public Task<IEnumerable<TimerInfo>> GetTimersAsync(CancellationToken cancellationToken)
{
- return Task.FromResult((IEnumerable<TimerInfo>)_timerProvider.GetAll());
+ var excludeStatues = new List<RecordingStatus>
+ {
+ RecordingStatus.Completed
+ };
+
+ var timers = _timerProvider.GetAll()
+ .Where(i => !excludeStatues.Contains(i.Status));
+
+ return Task.FromResult(timers);
}
public Task<SeriesTimerInfo> GetNewTimerDefaultsAsync(CancellationToken cancellationToken, ProgramInfo program = null)
@@ -581,7 +743,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
PrePaddingSeconds = Math.Max(config.PrePaddingSeconds, 0),
RecordAnyChannel = true,
RecordAnyTime = true,
- RecordNewOnly = false,
+ RecordNewOnly = true,
Days = new List<DayOfWeek>
{
@@ -601,6 +763,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
defaults.ProgramId = program.Id;
}
+ defaults.SkipEpisodesInLibrary = true;
+ defaults.KeepUntil = KeepUntil.UntilDeleted;
+
return Task.FromResult(defaults);
}
@@ -717,46 +882,108 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
throw new NotImplementedException();
}
+ private readonly SemaphoreSlim _liveStreamsSemaphore = new SemaphoreSlim(1, 1);
+ private readonly List<LiveStream> _liveStreams = new List<LiveStream>();
+
public async Task<MediaSourceInfo> GetChannelStream(string channelId, string streamId, CancellationToken cancellationToken)
{
- _logger.Info("Streaming Channel " + channelId);
+ var result = await GetChannelStreamWithDirectStreamProvider(channelId, streamId, cancellationToken).ConfigureAwait(false);
- foreach (var hostInstance in _liveTvManager.TunerHosts)
- {
- try
- {
- var result = await hostInstance.GetChannelStream(channelId, streamId, cancellationToken).ConfigureAwait(false);
+ return result.Item1;
+ }
- result.Item2.Release();
+ public async Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> GetChannelStreamWithDirectStreamProvider(string channelId, string streamId, CancellationToken cancellationToken)
+ {
+ var result = await GetChannelStreamInternal(channelId, streamId, cancellationToken).ConfigureAwait(false);
- return result.Item1;
- }
- catch (Exception e)
- {
- _logger.ErrorException("Error getting channel stream", e);
- }
+ return new Tuple<MediaSourceInfo, IDirectStreamProvider>(result.Item2, result.Item1 as IDirectStreamProvider);
+ }
+
+ private MediaSourceInfo CloneMediaSource(MediaSourceInfo mediaSource, bool enableStreamSharing)
+ {
+ var json = _jsonSerializer.SerializeToString(mediaSource);
+ mediaSource = _jsonSerializer.DeserializeFromString<MediaSourceInfo>(json);
+
+ mediaSource.Id = Guid.NewGuid().ToString("N") + "_" + mediaSource.Id;
+
+ //if (mediaSource.DateLiveStreamOpened.HasValue && enableStreamSharing)
+ //{
+ // var ticks = (DateTime.UtcNow - mediaSource.DateLiveStreamOpened.Value).Ticks - TimeSpan.FromSeconds(10).Ticks;
+ // ticks = Math.Max(0, ticks);
+ // mediaSource.Path += "?t=" + ticks.ToString(CultureInfo.InvariantCulture) + "&s=" + mediaSource.DateLiveStreamOpened.Value.Ticks.ToString(CultureInfo.InvariantCulture);
+ //}
+
+ return mediaSource;
+ }
+
+ public async Task<LiveStream> GetLiveStream(string uniqueId)
+ {
+ await _liveStreamsSemaphore.WaitAsync().ConfigureAwait(false);
+
+ try
+ {
+ return _liveStreams
+ .FirstOrDefault(i => string.Equals(i.UniqueId, uniqueId, StringComparison.OrdinalIgnoreCase));
+ }
+ finally
+ {
+ _liveStreamsSemaphore.Release();
}
- throw new ApplicationException("Tuner not found.");
}
- private async Task<Tuple<MediaSourceInfo, ITunerHost, SemaphoreSlim>> GetChannelStreamInternal(string channelId, string streamId, CancellationToken cancellationToken)
+ private async Task<Tuple<LiveStream, MediaSourceInfo, ITunerHost>> GetChannelStreamInternal(string channelId, string streamId, CancellationToken cancellationToken)
{
_logger.Info("Streaming Channel " + channelId);
- foreach (var hostInstance in _liveTvManager.TunerHosts)
+ await _liveStreamsSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
+
+ var result = _liveStreams.FirstOrDefault(i => string.Equals(i.OriginalStreamId, streamId, StringComparison.OrdinalIgnoreCase));
+
+ if (result != null && result.EnableStreamSharing)
{
- try
- {
- var result = await hostInstance.GetChannelStream(channelId, streamId, cancellationToken).ConfigureAwait(false);
+ var openedMediaSource = CloneMediaSource(result.OpenedMediaSource, result.EnableStreamSharing);
+ result.SharedStreamIds.Add(openedMediaSource.Id);
+ _liveStreamsSemaphore.Release();
- return new Tuple<MediaSourceInfo, ITunerHost, SemaphoreSlim>(result.Item1, hostInstance, result.Item2);
- }
- catch (Exception e)
+ _logger.Info("Live stream {0} consumer count is now {1}", streamId, result.ConsumerCount);
+
+ return new Tuple<LiveStream, MediaSourceInfo, ITunerHost>(result, openedMediaSource, result.TunerHost);
+ }
+
+ try
+ {
+ foreach (var hostInstance in _liveTvManager.TunerHosts)
{
- _logger.ErrorException("Error getting channel stream", e);
+ try
+ {
+ result = await hostInstance.GetChannelStream(channelId, streamId, cancellationToken).ConfigureAwait(false);
+
+ var openedMediaSource = CloneMediaSource(result.OpenedMediaSource, result.EnableStreamSharing);
+
+ result.SharedStreamIds.Add(openedMediaSource.Id);
+ _liveStreams.Add(result);
+
+ result.TunerHost = hostInstance;
+ result.OriginalStreamId = streamId;
+
+ _logger.Info("Returning mediasource streamId {0}, mediaSource.Id {1}, mediaSource.LiveStreamId {2}",
+ streamId, openedMediaSource.Id, openedMediaSource.LiveStreamId);
+
+ return new Tuple<LiveStream, MediaSourceInfo, ITunerHost>(result, openedMediaSource, hostInstance);
+ }
+ catch (FileNotFoundException)
+ {
+ }
+ catch (OperationCanceledException)
+ {
+ }
}
}
+ finally
+ {
+ _liveStreamsSemaphore.Release();
+ }
throw new ApplicationException("Tuner not found.");
}
@@ -783,14 +1010,82 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
throw new NotImplementedException();
}
- public Task<List<MediaSourceInfo>> GetRecordingStreamMediaSources(string recordingId, CancellationToken cancellationToken)
+ public async Task<List<MediaSourceInfo>> GetRecordingStreamMediaSources(string recordingId, CancellationToken cancellationToken)
{
- throw new NotImplementedException();
+ ActiveRecordingInfo info;
+
+ recordingId = recordingId.Replace("recording", string.Empty);
+
+ if (_activeRecordings.TryGetValue(recordingId, out info))
+ {
+ var stream = new MediaSourceInfo
+ {
+ Path = _appHost.GetLocalApiUrl("localhost") + "/LiveTv/LiveRecordings/" + recordingId + "/stream",
+ Id = recordingId,
+ SupportsDirectPlay = false,
+ SupportsDirectStream = true,
+ SupportsTranscoding = true,
+ IsInfiniteStream = true,
+ RequiresOpening = false,
+ RequiresClosing = false,
+ Protocol = Model.MediaInfo.MediaProtocol.Http,
+ BufferMs = 0
+ };
+
+ var isAudio = false;
+ await new LiveStreamHelper(_mediaEncoder, _logger).AddMediaInfoWithProbe(stream, isAudio, cancellationToken).ConfigureAwait(false);
+
+ return new List<MediaSourceInfo>
+ {
+ stream
+ };
+ }
+
+ throw new FileNotFoundException();
}
- public Task CloseLiveStream(string id, CancellationToken cancellationToken)
+ public async Task CloseLiveStream(string id, CancellationToken cancellationToken)
{
- return Task.FromResult(0);
+ // Ignore the consumer id
+ //id = id.Substring(id.IndexOf('_') + 1);
+
+ await _liveStreamsSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
+
+ try
+ {
+ var stream = _liveStreams.FirstOrDefault(i => i.SharedStreamIds.Contains(id));
+ if (stream != null)
+ {
+ stream.SharedStreamIds.Remove(id);
+
+ _logger.Info("Live stream {0} consumer count is now {1}", id, stream.ConsumerCount);
+
+ if (stream.ConsumerCount <= 0)
+ {
+ _liveStreams.Remove(stream);
+
+ _logger.Info("Closing live stream {0}", id);
+
+ await stream.Close().ConfigureAwait(false);
+ _logger.Info("Live stream {0} closed successfully", id);
+ }
+ }
+ else
+ {
+ _logger.Warn("Live stream not found: {0}, unable to close", id);
+ }
+ }
+ catch (OperationCanceledException)
+ {
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error closing live stream", ex);
+ }
+ finally
+ {
+ _liveStreamsSemaphore.Release();
+ }
}
public Task RecordLiveStream(string id, CancellationToken cancellationToken)
@@ -815,14 +1110,15 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
if (recordingEndDate <= DateTime.UtcNow)
{
- _logger.Warn("Recording timer fired for timer {0}, Id: {1}, but the program has already ended.", timer.Name, timer.Id);
+ _logger.Warn("Recording timer fired for updatedTimer {0}, Id: {1}, but the program has already ended.", timer.Name, timer.Id);
+ OnTimerOutOfDate(timer);
return;
}
var activeRecordingInfo = new ActiveRecordingInfo
{
CancellationTokenSource = new CancellationTokenSource(),
- TimerId = timer.Id
+ Timer = timer
};
if (_activeRecordings.TryAdd(timer.Id, activeRecordingInfo))
@@ -844,12 +1140,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
}
}
- private string GetRecordingPath(TimerInfo timer, ProgramInfo info)
+ private string GetRecordingPath(TimerInfo timer, out string seriesPath)
{
var recordPath = RecordingPath;
var config = GetConfiguration();
+ seriesPath = null;
- if (info.IsSeries)
+ if (timer.IsProgramSeries)
{
var customRecordingPath = config.SeriesRecordingPath;
var allowSubfolder = true;
@@ -864,29 +1161,20 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
recordPath = Path.Combine(recordPath, "Series");
}
- var folderName = _fileSystem.GetValidFilename(info.Name).Trim();
- var folderNameWithYear = folderName;
- if (info.ProductionYear.HasValue)
- {
- folderNameWithYear += " (" + info.ProductionYear.Value.ToString(CultureInfo.InvariantCulture) + ")";
- }
+ var folderName = _fileSystem.GetValidFilename(timer.Name).Trim();
- if (Directory.Exists(Path.Combine(recordPath, folderName)))
- {
- recordPath = Path.Combine(recordPath, folderName);
- }
- else
- {
- recordPath = Path.Combine(recordPath, folderNameWithYear);
- }
+ // Can't use the year here in the folder name because it is the year of the episode, not the series.
+ recordPath = Path.Combine(recordPath, folderName);
+
+ seriesPath = recordPath;
- if (info.SeasonNumber.HasValue)
+ if (timer.SeasonNumber.HasValue)
{
- folderName = string.Format("Season {0}", info.SeasonNumber.Value.ToString(CultureInfo.InvariantCulture));
+ folderName = string.Format("Season {0}", timer.SeasonNumber.Value.ToString(CultureInfo.InvariantCulture));
recordPath = Path.Combine(recordPath, folderName);
}
}
- else if (info.IsMovie)
+ else if (timer.IsMovie)
{
var customRecordingPath = config.MovieRecordingPath;
var allowSubfolder = true;
@@ -901,34 +1189,34 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
recordPath = Path.Combine(recordPath, "Movies");
}
- var folderName = _fileSystem.GetValidFilename(info.Name).Trim();
- if (info.ProductionYear.HasValue)
+ var folderName = _fileSystem.GetValidFilename(timer.Name).Trim();
+ if (timer.ProductionYear.HasValue)
{
- folderName += " (" + info.ProductionYear.Value.ToString(CultureInfo.InvariantCulture) + ")";
+ folderName += " (" + timer.ProductionYear.Value.ToString(CultureInfo.InvariantCulture) + ")";
}
recordPath = Path.Combine(recordPath, folderName);
}
- else if (info.IsKids)
+ else if (timer.IsKids)
{
if (config.EnableRecordingSubfolders)
{
recordPath = Path.Combine(recordPath, "Kids");
}
- var folderName = _fileSystem.GetValidFilename(info.Name).Trim();
- if (info.ProductionYear.HasValue)
+ var folderName = _fileSystem.GetValidFilename(timer.Name).Trim();
+ if (timer.ProductionYear.HasValue)
{
- folderName += " (" + info.ProductionYear.Value.ToString(CultureInfo.InvariantCulture) + ")";
+ folderName += " (" + timer.ProductionYear.Value.ToString(CultureInfo.InvariantCulture) + ")";
}
recordPath = Path.Combine(recordPath, folderName);
}
- else if (info.IsSports)
+ else if (timer.IsSports)
{
if (config.EnableRecordingSubfolders)
{
recordPath = Path.Combine(recordPath, "Sports");
}
- recordPath = Path.Combine(recordPath, _fileSystem.GetValidFilename(info.Name).Trim());
+ recordPath = Path.Combine(recordPath, _fileSystem.GetValidFilename(timer.Name).Trim());
}
else
{
@@ -936,54 +1224,57 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{
recordPath = Path.Combine(recordPath, "Other");
}
- recordPath = Path.Combine(recordPath, _fileSystem.GetValidFilename(info.Name).Trim());
+ recordPath = Path.Combine(recordPath, _fileSystem.GetValidFilename(timer.Name).Trim());
}
- var recordingFileName = _fileSystem.GetValidFilename(RecordingHelper.GetRecordingName(timer, info)).Trim() + ".ts";
+ var recordingFileName = _fileSystem.GetValidFilename(RecordingHelper.GetRecordingName(timer)).Trim() + ".ts";
return Path.Combine(recordPath, recordingFileName);
}
- private async Task RecordStream(TimerInfo timer, DateTime recordingEndDate, ActiveRecordingInfo activeRecordingInfo, CancellationToken cancellationToken)
+ private async Task RecordStream(TimerInfo timer, DateTime recordingEndDate,
+ ActiveRecordingInfo activeRecordingInfo, CancellationToken cancellationToken)
{
if (timer == null)
{
throw new ArgumentNullException("timer");
}
- ProgramInfo info = null;
+ ProgramInfo programInfo = null;
- if (string.IsNullOrWhiteSpace(timer.ProgramId))
+ if (!string.IsNullOrWhiteSpace(timer.ProgramId))
{
- _logger.Info("Timer {0} has null programId", timer.Id);
+ programInfo = GetProgramInfoFromCache(timer.ChannelId, timer.ProgramId);
}
- else
- {
- info = GetProgramInfoFromCache(timer.ChannelId, timer.ProgramId);
- }
-
- if (info == null)
+ if (programInfo == null)
{
_logger.Info("Unable to find program with Id {0}. Will search using start date", timer.ProgramId);
- info = GetProgramInfoFromCache(timer.ChannelId, timer.StartDate);
+ programInfo = GetProgramInfoFromCache(timer.ChannelId, timer.StartDate);
}
- if (info == null)
+ if (programInfo != null)
{
- throw new InvalidOperationException(string.Format("Program with Id {0} not found", timer.ProgramId));
+ RecordingHelper.CopyProgramInfoToTimerInfo(programInfo, timer);
+ activeRecordingInfo.Program = programInfo;
}
- var recordPath = GetRecordingPath(timer, info);
+ string seriesPath = null;
+ var recordPath = GetRecordingPath(timer, out seriesPath);
var recordingStatus = RecordingStatus.New;
- var isResourceOpen = false;
- SemaphoreSlim semaphore = null;
+
+ string liveStreamId = null;
+
+ OnRecordingStatusChanged();
try
{
- var result = await GetChannelStreamInternal(timer.ChannelId, null, CancellationToken.None).ConfigureAwait(false);
- isResourceOpen = true;
- semaphore = result.Item3;
- var mediaStreamInfo = result.Item1;
+ var allMediaSources = await GetChannelStreamMediaSources(timer.ChannelId, CancellationToken.None).ConfigureAwait(false);
+
+ var liveStreamInfo = await GetChannelStreamInternal(timer.ChannelId, allMediaSources[0].Id, CancellationToken.None)
+ .ConfigureAwait(false);
+
+ var mediaStreamInfo = liveStreamInfo.Item2;
+ liveStreamId = mediaStreamInfo.Id;
// HDHR doesn't seem to release the tuner right away after first probing with ffmpeg
//await Task.Delay(3000, cancellationToken).ConfigureAwait(false);
@@ -1000,7 +1291,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
var duration = recordingEndDate - DateTime.UtcNow;
- _logger.Info("Beginning recording. Will record for {0} minutes.", duration.TotalMinutes.ToString(CultureInfo.InvariantCulture));
+ _logger.Info("Beginning recording. Will record for {0} minutes.",
+ duration.TotalMinutes.ToString(CultureInfo.InvariantCulture));
_logger.Info("Writing file to path: " + recordPath);
_logger.Info("Opening recording stream from tuner provider");
@@ -1010,20 +1302,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
timer.Status = RecordingStatus.InProgress;
_timerProvider.AddOrUpdate(timer, false);
- result.Item3.Release();
- isResourceOpen = false;
+ SaveNfo(timer, recordPath, seriesPath);
+ EnforceKeepUpTo(timer);
};
- var pathWithDuration = result.Item2.ApplyDuration(mediaStreamInfo.Path, duration);
-
- // If it supports supplying duration via url
- if (!string.Equals(pathWithDuration, mediaStreamInfo.Path, StringComparison.OrdinalIgnoreCase))
- {
- mediaStreamInfo.Path = pathWithDuration;
- mediaStreamInfo.RunTimeTicks = duration.Ticks;
- }
-
- await recorder.Record(mediaStreamInfo, recordPath, duration, onStarted, cancellationToken).ConfigureAwait(false);
+ await recorder.Record(mediaStreamInfo, recordPath, duration, onStarted, cancellationToken)
+ .ConfigureAwait(false);
recordingStatus = RecordingStatus.Completed;
_logger.Info("Recording completed: {0}", recordPath);
@@ -1038,28 +1322,26 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
_logger.ErrorException("Error recording to {0}", ex, recordPath);
recordingStatus = RecordingStatus.Error;
}
- finally
+
+ if (!string.IsNullOrWhiteSpace(liveStreamId))
{
- if (isResourceOpen && semaphore != null)
+ try
{
- semaphore.Release();
+ await CloseLiveStream(liveStreamId, CancellationToken.None).ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error closing live stream", ex);
}
-
- _libraryManager.UnRegisterIgnoredPath(recordPath);
- _libraryMonitor.ReportFileSystemChangeComplete(recordPath, true);
-
- ActiveRecordingInfo removed;
- _activeRecordings.TryRemove(timer.Id, out removed);
}
- if (recordingStatus == RecordingStatus.Completed)
- {
- timer.Status = RecordingStatus.Completed;
- _timerProvider.Delete(timer);
+ _libraryManager.UnRegisterIgnoredPath(recordPath);
+ _libraryMonitor.ReportFileSystemChangeComplete(recordPath, true);
- OnSuccessfulRecording(info.IsSeries, recordPath);
- }
- else if (DateTime.UtcNow < timer.EndDate)
+ ActiveRecordingInfo removed;
+ _activeRecordings.TryRemove(timer.Id, out removed);
+
+ if (recordingStatus != RecordingStatus.Completed && DateTime.UtcNow < timer.EndDate)
{
const int retryIntervalSeconds = 60;
_logger.Info("Retrying recording in {0} seconds.", retryIntervalSeconds);
@@ -1068,10 +1350,123 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
timer.StartDate = DateTime.UtcNow.AddSeconds(retryIntervalSeconds);
_timerProvider.AddOrUpdate(timer);
}
+ else if (File.Exists(recordPath))
+ {
+ timer.RecordingPath = recordPath;
+ timer.Status = RecordingStatus.Completed;
+ _timerProvider.AddOrUpdate(timer, false);
+ OnSuccessfulRecording(timer, recordPath);
+ }
else
{
_timerProvider.Delete(timer);
}
+
+ OnRecordingStatusChanged();
+ }
+
+ private void OnRecordingStatusChanged()
+ {
+ EventHelper.FireEventIfNotNull(RecordingStatusChanged, this, new RecordingStatusChangedEventArgs
+ {
+
+ }, _logger);
+ }
+
+ private async void EnforceKeepUpTo(TimerInfo timer)
+ {
+ if (string.IsNullOrWhiteSpace(timer.SeriesTimerId))
+ {
+ return;
+ }
+
+ var seriesTimerId = timer.SeriesTimerId;
+ var seriesTimer = _seriesTimerProvider.GetAll().FirstOrDefault(i => string.Equals(i.Id, seriesTimerId, StringComparison.OrdinalIgnoreCase));
+
+ if (seriesTimer == null || seriesTimer.KeepUpTo <= 1)
+ {
+ return;
+ }
+
+ if (_disposed)
+ {
+ return;
+ }
+
+ await _recordingDeleteSemaphore.WaitAsync().ConfigureAwait(false);
+
+ try
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ var timersToDelete = _timerProvider.GetAll()
+ .Where(i => i.Status == RecordingStatus.Completed && !string.IsNullOrWhiteSpace(i.RecordingPath))
+ .Where(i => string.Equals(i.SeriesTimerId, seriesTimerId, StringComparison.OrdinalIgnoreCase))
+ .OrderByDescending(i => i.EndDate)
+ .Where(i => File.Exists(i.RecordingPath))
+ .Skip(seriesTimer.KeepUpTo - 1)
+ .ToList();
+
+ await DeleteLibraryItemsForTimers(timersToDelete).ConfigureAwait(false);
+ }
+ finally
+ {
+ _recordingDeleteSemaphore.Release();
+ }
+ }
+
+ private readonly SemaphoreSlim _recordingDeleteSemaphore = new SemaphoreSlim(1, 1);
+ private async Task DeleteLibraryItemsForTimers(List<TimerInfo> timers)
+ {
+ foreach (var timer in timers)
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ try
+ {
+ await DeleteLibraryItemForTimer(timer).ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error deleting recording", ex);
+ }
+ }
+ }
+
+ private async Task DeleteLibraryItemForTimer(TimerInfo timer)
+ {
+ var libraryItem = _libraryManager.FindByPath(timer.RecordingPath, false);
+
+ if (libraryItem != null)
+ {
+ await _libraryManager.DeleteItem(libraryItem, new DeleteOptions
+ {
+ DeleteFileLocation = true
+ });
+ }
+ else
+ {
+ try
+ {
+ File.Delete(timer.RecordingPath);
+ }
+ catch (DirectoryNotFoundException)
+ {
+
+ }
+ catch (FileNotFoundException)
+ {
+
+ }
+ }
+
+ _timerProvider.Delete(timer);
}
private string EnsureFileUnique(string path, string timerId)
@@ -1099,7 +1494,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
return true;
}
- var hasRecordingAtPath = _activeRecordings.Values.ToList().Any(i => string.Equals(i.Path, path, StringComparison.OrdinalIgnoreCase) && !string.Equals(i.TimerId, timerId, StringComparison.OrdinalIgnoreCase));
+ var hasRecordingAtPath = _activeRecordings
+ .Values
+ .ToList()
+ .Any(i => string.Equals(i.Path, path, StringComparison.OrdinalIgnoreCase) && !string.Equals(i.Timer.Id, timerId, StringComparison.OrdinalIgnoreCase));
if (hasRecordingAtPath)
{
@@ -1125,30 +1523,180 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
return new DirectRecorder(_logger, _httpClient, _fileSystem);
}
- private async void OnSuccessfulRecording(bool isSeries, string path)
+ private async void OnSuccessfulRecording(TimerInfo timer, string path)
{
- if (GetConfiguration().EnableAutoOrganize)
+ if (timer.IsProgramSeries && GetConfiguration().EnableAutoOrganize)
{
- if (isSeries)
+ try
{
- try
+ // this is to account for the library monitor holding a lock for additional time after the change is complete.
+ // ideally this shouldn't be hard-coded
+ await Task.Delay(30000).ConfigureAwait(false);
+
+ var organize = new EpisodeFileOrganizer(_organizationService, _config, _fileSystem, _logger, _libraryManager, _libraryMonitor, _providerManager);
+
+ var result = await organize.OrganizeEpisodeFile(path, _config.GetAutoOrganizeOptions(), false, CancellationToken.None).ConfigureAwait(false);
+
+ if (result.Status == FileSortingStatus.Success)
{
- // this is to account for the library monitor holding a lock for additional time after the change is complete.
- // ideally this shouldn't be hard-coded
- await Task.Delay(30000).ConfigureAwait(false);
+ return;
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error processing new recording", ex);
+ }
+ }
+ }
- var organize = new EpisodeFileOrganizer(_organizationService, _config, _fileSystem, _logger, _libraryManager, _libraryMonitor, _providerManager);
+ private void SaveNfo(TimerInfo timer, string recordingPath, string seriesPath)
+ {
+ try
+ {
+ if (timer.IsProgramSeries)
+ {
+ SaveSeriesNfo(timer, recordingPath, seriesPath);
+ }
+ else if (!timer.IsMovie || timer.IsSports || timer.IsNews)
+ {
+ SaveVideoNfo(timer, recordingPath);
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error saving nfo", ex);
+ }
+ }
+
+ private void SaveSeriesNfo(TimerInfo timer, string recordingPath, string seriesPath)
+ {
+ var nfoPath = Path.Combine(seriesPath, "tvshow.nfo");
+
+ if (File.Exists(nfoPath))
+ {
+ return;
+ }
- var result = await organize.OrganizeEpisodeFile(path, _config.GetAutoOrganizeOptions(), false, CancellationToken.None).ConfigureAwait(false);
+ using (var stream = _fileSystem.GetFileStream(nfoPath, FileMode.Create, FileAccess.Write, FileShare.Read))
+ {
+ var settings = new XmlWriterSettings
+ {
+ Indent = true,
+ Encoding = Encoding.UTF8,
+ CloseOutput = false
+ };
+
+ using (XmlWriter writer = XmlWriter.Create(stream, settings))
+ {
+ writer.WriteStartDocument(true);
+ writer.WriteStartElement("tvshow");
+
+ if (!string.IsNullOrWhiteSpace(timer.Name))
+ {
+ writer.WriteElementString("title", timer.Name);
}
- catch (Exception ex)
+
+ writer.WriteEndElement();
+ writer.WriteEndDocument();
+ }
+ }
+ }
+
+ public const string DateAddedFormat = "yyyy-MM-dd HH:mm:ss";
+ private void SaveVideoNfo(TimerInfo timer, string recordingPath)
+ {
+ var nfoPath = Path.ChangeExtension(recordingPath, ".nfo");
+
+ if (File.Exists(nfoPath))
+ {
+ return;
+ }
+
+ using (var stream = _fileSystem.GetFileStream(nfoPath, FileMode.Create, FileAccess.Write, FileShare.Read))
+ {
+ var settings = new XmlWriterSettings
+ {
+ Indent = true,
+ Encoding = Encoding.UTF8,
+ CloseOutput = false
+ };
+
+ using (XmlWriter writer = XmlWriter.Create(stream, settings))
+ {
+ writer.WriteStartDocument(true);
+ writer.WriteStartElement("movie");
+
+ if (!string.IsNullOrWhiteSpace(timer.Name))
+ {
+ writer.WriteElementString("title", timer.Name);
+ }
+
+ writer.WriteElementString("dateadded", DateTime.UtcNow.ToLocalTime().ToString(DateAddedFormat));
+
+ if (timer.ProductionYear.HasValue)
+ {
+ writer.WriteElementString("year", timer.ProductionYear.Value.ToString(CultureInfo.InvariantCulture));
+ }
+ if (!string.IsNullOrEmpty(timer.OfficialRating))
+ {
+ writer.WriteElementString("mpaa", timer.OfficialRating);
+ }
+
+ var overview = (timer.Overview ?? string.Empty)
+ .StripHtml()
+ .Replace("&quot;", "'");
+
+ writer.WriteElementString("plot", overview);
+ writer.WriteElementString("lockdata", true.ToString().ToLower());
+
+ if (timer.CommunityRating.HasValue)
+ {
+ writer.WriteElementString("rating", timer.CommunityRating.Value.ToString(CultureInfo.InvariantCulture));
+ }
+
+ if (timer.IsSports)
+ {
+ AddGenre(timer.Genres, "Sports");
+ }
+ if (timer.IsKids)
+ {
+ AddGenre(timer.Genres, "Kids");
+ AddGenre(timer.Genres, "Children");
+ }
+ if (timer.IsNews)
+ {
+ AddGenre(timer.Genres, "News");
+ }
+
+ foreach (var genre in timer.Genres)
+ {
+ writer.WriteElementString("genre", genre);
+ }
+
+ if (!string.IsNullOrWhiteSpace(timer.ShortOverview))
+ {
+ writer.WriteElementString("outline", timer.ShortOverview);
+ }
+
+ if (!string.IsNullOrWhiteSpace(timer.HomePageUrl))
{
- _logger.ErrorException("Error processing new recording", ex);
+ writer.WriteElementString("website", timer.HomePageUrl);
}
+
+ writer.WriteEndElement();
+ writer.WriteEndDocument();
}
}
}
+ private void AddGenre(List<string> genres, string genre)
+ {
+ if (!genres.Contains(genre, StringComparer.OrdinalIgnoreCase))
+ {
+ genres.Add(genre);
+ }
+ }
+
private ProgramInfo GetProgramInfoFromCache(string channelId, string programId)
{
var epgData = GetEpgDataForChannel(channelId);
@@ -1168,41 +1716,101 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
return _config.GetConfiguration<LiveTvOptions>("livetv");
}
+ private bool ShouldCancelTimerForSeriesTimer(SeriesTimerInfo seriesTimer, TimerInfo timer)
+ {
+ if (!seriesTimer.RecordAnyTime)
+ {
+ if (Math.Abs(seriesTimer.StartDate.TimeOfDay.Ticks - timer.StartDate.TimeOfDay.Ticks) >= TimeSpan.FromMinutes(5).Ticks)
+ {
+ return true;
+ }
+
+ if (!seriesTimer.Days.Contains(timer.StartDate.ToLocalTime().DayOfWeek))
+ {
+ return true;
+ }
+ }
+
+ if (seriesTimer.RecordNewOnly && timer.IsRepeat)
+ {
+ return true;
+ }
+
+ if (!seriesTimer.RecordAnyChannel && !string.Equals(timer.ChannelId, seriesTimer.ChannelId, StringComparison.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+
+ return seriesTimer.SkipEpisodesInLibrary && IsProgramAlreadyInLibrary(timer);
+ }
+
private async Task UpdateTimersForSeriesTimer(List<ProgramInfo> epgData, SeriesTimerInfo seriesTimer, bool deleteInvalidTimers)
{
- var newTimers = GetTimersForSeries(seriesTimer, epgData, true).ToList();
-
+ var allTimers = GetTimersForSeries(seriesTimer, epgData)
+ .ToList();
+
var registration = await _liveTvManager.GetRegistrationInfo("seriesrecordings").ConfigureAwait(false);
if (registration.IsValid)
{
- foreach (var timer in newTimers)
+ foreach (var timer in allTimers)
{
- _timerProvider.AddOrUpdate(timer);
+ var existingTimer = _timerProvider.GetTimer(timer.Id);
+
+ if (existingTimer == null)
+ {
+ if (ShouldCancelTimerForSeriesTimer(seriesTimer, timer))
+ {
+ timer.Status = RecordingStatus.Cancelled;
+ }
+ _timerProvider.Add(timer);
+ }
+ else
+ {
+ // Only update if not currently active
+ ActiveRecordingInfo activeRecordingInfo;
+ if (!_activeRecordings.TryGetValue(timer.Id, out activeRecordingInfo))
+ {
+ UpdateExistingTimerWithNewMetadata(existingTimer, timer);
+
+ if (ShouldCancelTimerForSeriesTimer(seriesTimer, timer))
+ {
+ existingTimer.Status = RecordingStatus.Cancelled;
+ }
+
+ existingTimer.SeriesTimerId = seriesTimer.Id;
+ _timerProvider.Update(existingTimer);
+ }
+ }
}
}
if (deleteInvalidTimers)
{
- var allTimers = GetTimersForSeries(seriesTimer, epgData, false)
+ var allTimerIds = allTimers
.Select(i => i.Id)
.ToList();
+ var deleteStatuses = new List<RecordingStatus>
+ {
+ RecordingStatus.New
+ };
+
var deletes = _timerProvider.GetAll()
.Where(i => string.Equals(i.SeriesTimerId, seriesTimer.Id, StringComparison.OrdinalIgnoreCase))
- .Where(i => !allTimers.Contains(i.Id, StringComparer.OrdinalIgnoreCase) && i.StartDate > DateTime.UtcNow)
+ .Where(i => !allTimerIds.Contains(i.Id, StringComparer.OrdinalIgnoreCase) && i.StartDate > DateTime.UtcNow)
+ .Where(i => deleteStatuses.Contains(i.Status))
.ToList();
foreach (var timer in deletes)
{
- await CancelTimerAsync(timer.Id, CancellationToken.None).ConfigureAwait(false);
+ CancelTimerInternal(timer.Id, false);
}
}
}
private IEnumerable<TimerInfo> GetTimersForSeries(SeriesTimerInfo seriesTimer,
- IEnumerable<ProgramInfo> allPrograms,
- bool filterByCurrentRecordings)
+ IEnumerable<ProgramInfo> allPrograms)
{
if (seriesTimer == null)
{
@@ -1214,19 +1822,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
}
// Exclude programs that have already ended
- allPrograms = allPrograms.Where(i => i.EndDate > DateTime.UtcNow && i.StartDate > DateTime.UtcNow);
+ allPrograms = allPrograms.Where(i => i.EndDate > DateTime.UtcNow);
allPrograms = GetProgramsForSeries(seriesTimer, allPrograms);
- if (filterByCurrentRecordings)
- {
- allPrograms = allPrograms.Where(i => !IsProgramAlreadyInLibrary(i));
- }
-
return allPrograms.Select(i => RecordingHelper.CreateTimer(i, seriesTimer));
}
- private bool IsProgramAlreadyInLibrary(ProgramInfo program)
+ private bool IsProgramAlreadyInLibrary(TimerInfo program)
{
if ((program.EpisodeNumber.HasValue && program.SeasonNumber.HasValue) || !string.IsNullOrWhiteSpace(program.EpisodeTitle))
{
@@ -1281,23 +1884,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
private IEnumerable<ProgramInfo> GetProgramsForSeries(SeriesTimerInfo seriesTimer, IEnumerable<ProgramInfo> allPrograms)
{
- if (!seriesTimer.RecordAnyTime)
- {
- allPrograms = allPrograms.Where(epg => Math.Abs(seriesTimer.StartDate.TimeOfDay.Ticks - epg.StartDate.TimeOfDay.Ticks) < TimeSpan.FromMinutes(5).Ticks);
-
- allPrograms = allPrograms.Where(i => seriesTimer.Days.Contains(i.StartDate.ToLocalTime().DayOfWeek));
- }
-
- if (seriesTimer.RecordNewOnly)
- {
- allPrograms = allPrograms.Where(epg => !epg.IsRepeat);
- }
-
- if (!seriesTimer.RecordAnyChannel)
- {
- allPrograms = allPrograms.Where(epg => string.Equals(epg.ChannelId, seriesTimer.ChannelId, StringComparison.OrdinalIgnoreCase));
- }
-
if (string.IsNullOrWhiteSpace(seriesTimer.SeriesId))
{
_logger.Error("seriesTimer.SeriesId is null. Cannot find programs for series");
@@ -1341,8 +1927,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
return channelIds.SelectMany(GetEpgDataForChannel).ToList();
}
+ private bool _disposed;
public void Dispose()
{
+ _disposed = true;
foreach (var pair in _activeRecordings.ToList())
{
pair.Value.CancellationTokenSource.Cancel();
@@ -1393,7 +1981,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
class ActiveRecordingInfo
{
public string Path { get; set; }
- public string TimerId { get; set; }
+ public TimerInfo Timer { get; set; }
+ public ProgramInfo Program { get; set; }
public CancellationTokenSource CancellationTokenSource { get; set; }
}
}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
index 75ad3de59..3e9d186e3 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
@@ -68,36 +68,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
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 RecordWithTempFile(mediaSource, tempfile, targetFile, duration, onStarted, cancellationToken)
- .ConfigureAwait(false);
- }
- finally
- {
- try
- {
- File.Delete(tempfile);
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error deleting recording temp file", ex);
- }
- }
- }
-
- 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;
@@ -106,59 +76,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
_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()
- {
- Url = mediaSource.Path
- };
-
- httpRequestOptions.BufferContent = false;
-
- using (var response = await _httpClient.SendAsync(httpRequestOptions, "GET").ConfigureAwait(false))
- {
- _logger.Info("Opened recording stream from tuner provider");
-
- Directory.CreateDirectory(Path.GetDirectoryName(tempFile));
-
- using (var output = _fileSystem.GetFileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read))
- {
- //onStarted();
-
- _logger.Info("Copying recording stream to file {0}", tempFile);
-
- var bufferMs = 5000;
-
- if (mediaSource.RunTimeTicks.HasValue)
- {
- // The media source already has a fixed duration
- // But add another stop 1 minute later just in case the recording gets stuck for any reason
- var durationToken = new CancellationTokenSource(duration.Add(TimeSpan.FromMinutes(1)));
- cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
- }
- else
- {
- // The media source if infinite so we need to handle stopping ourselves
- var durationToken = new CancellationTokenSource(duration.Add(TimeSpan.FromMilliseconds(bufferMs)));
- cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
- }
-
- var tempFileTask = response.Content.CopyToAsync(output, StreamDefaults.DefaultCopyToBufferSize, cancellationToken);
-
- // Give the temp file a little time to build up
- await Task.Delay(bufferMs, cancellationToken).ConfigureAwait(false);
-
- var recordTask = Task.Run(() => RecordFromFile(mediaSource, tempFile, targetFile, duration, onStarted, cancellationToken), cancellationToken);
-
- await tempFileTask.ConfigureAwait(false);
-
- await recordTask.ConfigureAwait(false);
- }
- }
-
- _logger.Info("Recording completed to file {0}", targetFile);
- }
-
private Task RecordFromFile(MediaSourceInfo mediaSource, string inputFile, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
{
_targetPath = targetFile;
@@ -200,7 +117,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(_json.SerializeToString(mediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine);
_logFileStream.Write(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length);
- process.Exited += (sender, args) => OnFfMpegProcessExited(process);
+ process.Exited += (sender, args) => OnFfMpegProcessExited(process, inputFile);
process.Start();
@@ -214,6 +131,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
// Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
StartStreamingLog(process.StandardError.BaseStream, _logFileStream);
+ _logger.Info("ffmpeg recording process started for {0}", _targetPath);
+
return _taskCompletionSource.Task;
}
@@ -234,16 +153,33 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
}
var durationParam = " -t " + _mediaEncoder.GetTimeParameter(duration.Ticks);
- var commandLineArgs = "-fflags +genpts -async 1 -vsync -1 -re -i \"{0}\"{4} -sn {2} -map_metadata -1 -threads 0 {3} -y \"{1}\"";
+ var inputModifiers = "-fflags +genpts -async 1 -vsync -1";
+ var commandLineArgs = "-i \"{0}\"{4} -sn {2} -map_metadata -1 -threads 0 {3} -y \"{1}\"";
+
+ long startTimeTicks = 0;
+ //if (mediaSource.DateLiveStreamOpened.HasValue)
+ //{
+ // var elapsed = DateTime.UtcNow - mediaSource.DateLiveStreamOpened.Value;
+ // elapsed -= TimeSpan.FromSeconds(10);
+ // if (elapsed.TotalSeconds >= 0)
+ // {
+ // startTimeTicks = elapsed.Ticks + startTimeTicks;
+ // }
+ //}
if (mediaSource.ReadAtNativeFramerate)
{
- commandLineArgs = "-re " + commandLineArgs;
+ inputModifiers += " -re";
+ }
+
+ if (startTimeTicks > 0)
+ {
+ inputModifiers = "-ss " + _mediaEncoder.GetTimeParameter(startTimeTicks) + " " + inputModifiers;
}
commandLineArgs = string.Format(commandLineArgs, inputTempFile, targetFile, videoArgs, GetAudioArgs(mediaSource), durationParam);
- return commandLineArgs;
+ return inputModifiers + " " + commandLineArgs;
}
private string GetAudioArgs(MediaSourceInfo mediaSource)
@@ -309,8 +245,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
/// <summary>
/// Processes the exited.
/// </summary>
- /// <param name="process">The process.</param>
- private void OnFfMpegProcessExited(Process process)
+ private void OnFfMpegProcessExited(Process process, string inputFile)
{
_hasExited = true;
diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs
index 9485e0325..f7b4b3fde 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs
@@ -2,6 +2,7 @@
using MediaBrowser.Controller.LiveTv;
using System;
using System.Globalization;
+using MediaBrowser.Model.LiveTv;
namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{
@@ -12,37 +13,55 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
return timer.StartDate.AddSeconds(-timer.PrePaddingSeconds);
}
- public static TimerInfo CreateTimer(ProgramInfo parent, SeriesTimerInfo series)
+ public static TimerInfo CreateTimer(ProgramInfo parent, SeriesTimerInfo seriesTimer)
{
var timer = new TimerInfo();
timer.ChannelId = parent.ChannelId;
- timer.Id = (series.Id + parent.Id).GetMD5().ToString("N");
+ timer.Id = (seriesTimer.Id + parent.Id).GetMD5().ToString("N");
timer.StartDate = parent.StartDate;
timer.EndDate = parent.EndDate;
timer.ProgramId = parent.Id;
- timer.PrePaddingSeconds = series.PrePaddingSeconds;
- timer.PostPaddingSeconds = series.PostPaddingSeconds;
- timer.IsPostPaddingRequired = series.IsPostPaddingRequired;
- timer.IsPrePaddingRequired = series.IsPrePaddingRequired;
- timer.Priority = series.Priority;
+ timer.PrePaddingSeconds = seriesTimer.PrePaddingSeconds;
+ timer.PostPaddingSeconds = seriesTimer.PostPaddingSeconds;
+ timer.IsPostPaddingRequired = seriesTimer.IsPostPaddingRequired;
+ timer.IsPrePaddingRequired = seriesTimer.IsPrePaddingRequired;
+ timer.KeepUntil = seriesTimer.KeepUntil;
+ timer.Priority = seriesTimer.Priority;
timer.Name = parent.Name;
timer.Overview = parent.Overview;
- timer.SeriesTimerId = series.Id;
+ timer.SeriesTimerId = seriesTimer.Id;
+
+ CopyProgramInfoToTimerInfo(parent, timer);
return timer;
}
- public static string GetRecordingName(TimerInfo timer, ProgramInfo info)
+ public static void CopyProgramInfoToTimerInfo(ProgramInfo programInfo, TimerInfo timerInfo)
{
- if (info == null)
- {
- return timer.ProgramId;
- }
+ timerInfo.SeasonNumber = programInfo.SeasonNumber;
+ timerInfo.EpisodeNumber = programInfo.EpisodeNumber;
+ timerInfo.IsMovie = programInfo.IsMovie;
+ timerInfo.IsKids = programInfo.IsKids;
+ timerInfo.IsNews = programInfo.IsNews;
+ timerInfo.IsSports = programInfo.IsSports;
+ timerInfo.ProductionYear = programInfo.ProductionYear;
+ timerInfo.EpisodeTitle = programInfo.EpisodeTitle;
+ timerInfo.OriginalAirDate = programInfo.OriginalAirDate;
+ timerInfo.IsProgramSeries = programInfo.IsSeries;
+ timerInfo.HomePageUrl = programInfo.HomePageUrl;
+ timerInfo.CommunityRating = programInfo.CommunityRating;
+ timerInfo.ShortOverview = programInfo.ShortOverview;
+ timerInfo.OfficialRating = programInfo.OfficialRating;
+ timerInfo.IsRepeat = programInfo.IsRepeat;
+ }
+
+ public static string GetRecordingName(TimerInfo info)
+ {
var name = info.Name;
- if (info.IsSeries)
+ if (info.IsProgramSeries)
{
var addHyphen = true;
diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs
index 423358906..bddce0420 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs
@@ -9,7 +9,6 @@ using System.Globalization;
using System.Linq;
using System.Threading;
using CommonIO;
-using MediaBrowser.Controller.Power;
using MediaBrowser.Model.LiveTv;
namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
@@ -17,15 +16,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
public class TimerManager : ItemDataProvider<TimerInfo>
{
private readonly ConcurrentDictionary<string, Timer> _timers = new ConcurrentDictionary<string, Timer>(StringComparer.OrdinalIgnoreCase);
- private readonly IPowerManagement _powerManagement;
private readonly ILogger _logger;
public event EventHandler<GenericEventArgs<TimerInfo>> TimerFired;
- public TimerManager(IFileSystem fileSystem, IJsonSerializer jsonSerializer, ILogger logger, string dataPath, IPowerManagement powerManagement, ILogger logger1)
+ public TimerManager(IFileSystem fileSystem, IJsonSerializer jsonSerializer, ILogger logger, string dataPath, ILogger logger1)
: base(fileSystem, jsonSerializer, logger, dataPath, (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase))
{
- _powerManagement = powerManagement;
_logger = logger1;
}
@@ -35,7 +32,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
foreach (var item in GetAll().ToList())
{
- AddTimer(item);
+ AddOrUpdateSystemTimer(item);
}
}
@@ -58,18 +55,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
public override void Update(TimerInfo item)
{
base.Update(item);
-
- Timer timer;
- if (_timers.TryGetValue(item.Id, out timer))
- {
- var timespan = RecordingHelper.GetStartTime(item) - DateTime.UtcNow;
- timer.Change(timespan, TimeSpan.Zero);
- ScheduleWake(item);
- }
- else
- {
- AddTimer(item);
- }
+ AddOrUpdateSystemTimer(item);
}
public void AddOrUpdate(TimerInfo item, bool resetTimer)
@@ -100,13 +86,25 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
}
base.Add(item);
- AddTimer(item);
- ScheduleWake(item);
+ AddOrUpdateSystemTimer(item);
+ }
+
+ private bool ShouldStartTimer(TimerInfo item)
+ {
+ if (item.Status == RecordingStatus.Completed ||
+ item.Status == RecordingStatus.Cancelled)
+ {
+ return false;
+ }
+
+ return true;
}
- private void AddTimer(TimerInfo item)
+ private void AddOrUpdateSystemTimer(TimerInfo item)
{
- if (item.Status == RecordingStatus.Completed)
+ StopTimer(item);
+
+ if (!ShouldStartTimer(item))
{
return;
}
@@ -120,33 +118,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
return;
}
- var timerLength = startDate - now;
- StartTimer(item, timerLength);
+ var dueTime = startDate - now;
+ StartTimer(item, dueTime);
}
- private void ScheduleWake(TimerInfo info)
+ private void StartTimer(TimerInfo item, TimeSpan dueTime)
{
- var startDate = RecordingHelper.GetStartTime(info).AddMinutes(-5);
-
- try
- {
- _powerManagement.ScheduleWake(startDate);
- _logger.Info("Scheduled system wake timer at {0} (UTC)", startDate);
- }
- catch (NotImplementedException)
- {
-
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error scheduling wake timer", ex);
- }
- }
-
- public void StartTimer(TimerInfo item, TimeSpan dueTime)
- {
- StopTimer(item);
-
var timer = new Timer(TimerCallback, item.Id, dueTime, TimeSpan.Zero);
if (_timers.TryAdd(item.Id, timer))
@@ -179,5 +156,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
EventHelper.FireEventIfNotNull(TimerFired, this, new GenericEventArgs<TimerInfo> { Argument = timer }, Logger);
}
}
+
+ public TimerInfo GetTimer(string id)
+ {
+ return GetAll().FirstOrDefault(r => string.Equals(r.Id, id, StringComparison.OrdinalIgnoreCase));
+ }
}
}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs
index d1d8df2e8..d3549aef5 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs
@@ -11,6 +11,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Emby.XmlTv.Classes;
+using Emby.XmlTv.Entities;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
@@ -115,7 +116,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
var reader = new XmlTvReader(path, GetLanguage(), null);
var results = reader.GetProgrammes(channelNumber, startDateUtc, endDateUtc, cancellationToken);
- return results.Select(p => new ProgramInfo()
+ return results.Select(p => GetProgramInfo(p, info));
+ }
+
+ private ProgramInfo GetProgramInfo(XmlTvProgram p, ListingsProviderInfo info)
+ {
+ var programInfo = new ProgramInfo
{
ChannelId = p.ChannelId,
EndDate = GetDate(p.EndDate),
@@ -141,7 +147,16 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
OfficialRating = p.Rating != null && !String.IsNullOrEmpty(p.Rating.Value) ? p.Rating.Value : null,
CommunityRating = p.StarRating.HasValue ? p.StarRating.Value : (float?)null,
SeriesId = p.Episode != null ? p.Title.GetMD5().ToString("N") : null
- });
+ };
+
+ if (programInfo.IsMovie)
+ {
+ programInfo.IsSeries = false;
+ programInfo.EpisodeNumber = null;
+ programInfo.EpisodeTitle = null;
+ }
+
+ return programInfo;
}
private DateTime GetDate(DateTime date)
diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveStreamHelper.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveStreamHelper.cs
new file mode 100644
index 000000000..336c32bae
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/LiveTv/LiveStreamHelper.cs
@@ -0,0 +1,110 @@
+using System;
+using System.Globalization;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Model.Dlna;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Logging;
+
+namespace MediaBrowser.Server.Implementations.LiveTv
+{
+ public class LiveStreamHelper
+ {
+ private readonly IMediaEncoder _mediaEncoder;
+ private readonly ILogger _logger;
+
+ public LiveStreamHelper(IMediaEncoder mediaEncoder, ILogger logger)
+ {
+ _mediaEncoder = mediaEncoder;
+ _logger = logger;
+ }
+
+ public async Task AddMediaInfoWithProbe(MediaSourceInfo mediaSource, bool isAudio, CancellationToken cancellationToken)
+ {
+ var originalRuntime = mediaSource.RunTimeTicks;
+
+ var now = DateTime.UtcNow;
+
+ var info = await _mediaEncoder.GetMediaInfo(new MediaInfoRequest
+ {
+ InputPath = mediaSource.Path,
+ Protocol = mediaSource.Protocol,
+ MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video,
+ ExtractChapters = false,
+ AnalyzeDurationSections = 2
+
+ }, cancellationToken).ConfigureAwait(false);
+
+ _logger.Info("Live tv media info probe took {0} seconds", (DateTime.UtcNow - now).TotalSeconds.ToString(CultureInfo.InvariantCulture));
+
+ mediaSource.Bitrate = info.Bitrate;
+ mediaSource.Container = info.Container;
+ mediaSource.Formats = info.Formats;
+ mediaSource.MediaStreams = info.MediaStreams;
+ mediaSource.RunTimeTicks = info.RunTimeTicks;
+ mediaSource.Size = info.Size;
+ mediaSource.Timestamp = info.Timestamp;
+ mediaSource.Video3DFormat = info.Video3DFormat;
+ mediaSource.VideoType = info.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 = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == Model.Entities.MediaStreamType.Audio);
+
+ if (audioStream == null || audioStream.Index == -1)
+ {
+ mediaSource.DefaultAudioStreamIndex = null;
+ }
+ else
+ {
+ mediaSource.DefaultAudioStreamIndex = audioStream.Index;
+ }
+
+ var videoStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == Model.Entities.MediaStreamType.Video);
+ if (videoStream != null)
+ {
+ if (!videoStream.BitRate.HasValue)
+ {
+ var width = videoStream.Width ?? 1920;
+
+ if (width >= 1900)
+ {
+ videoStream.BitRate = 8000000;
+ }
+
+ else if (width >= 1260)
+ {
+ videoStream.BitRate = 3000000;
+ }
+
+ else if (width >= 700)
+ {
+ videoStream.BitRate = 1000000;
+ }
+ }
+
+ // This is coming up false and preventing stream copy
+ videoStream.IsAVC = null;
+ }
+
+ // Try to estimate this
+ if (!mediaSource.Bitrate.HasValue)
+ {
+ var total = mediaSource.MediaStreams.Select(i => i.BitRate ?? 0).Sum();
+
+ if (total > 0)
+ {
+ mediaSource.Bitrate = total;
+ }
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs
index 683377c61..8c46b4597 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs
@@ -10,6 +10,7 @@ using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Logging;
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -52,6 +53,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
PostPaddingSeconds = info.PostPaddingSeconds,
IsPostPaddingRequired = info.IsPostPaddingRequired,
IsPrePaddingRequired = info.IsPrePaddingRequired,
+ KeepUntil = info.KeepUntil,
ExternalChannelId = info.ChannelId,
ExternalSeriesTimerId = info.SeriesTimerId,
ServiceName = service.Name,
@@ -71,6 +73,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
dto.ProgramInfo = _dtoService.GetBaseItemDto(program, new DtoOptions());
dto.ProgramInfo.TimerId = dto.Id;
+
dto.ProgramInfo.SeriesTimerId = dto.SeriesTimerId;
}
@@ -100,6 +103,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv
Priority = info.Priority,
RecordAnyChannel = info.RecordAnyChannel,
RecordAnyTime = info.RecordAnyTime,
+ SkipEpisodesInLibrary = info.SkipEpisodesInLibrary,
+ KeepUpTo = info.KeepUpTo,
+ KeepUntil = info.KeepUntil,
RecordNewOnly = info.RecordNewOnly,
ExternalChannelId = info.ChannelId,
ExternalProgramId = info.ProgramId,
@@ -120,6 +126,34 @@ namespace MediaBrowser.Server.Implementations.LiveTv
dto.DayPattern = info.Days == null ? null : GetDayPattern(info.Days);
+ if (!string.IsNullOrWhiteSpace(info.SeriesId))
+ {
+ var program = _libraryManager.GetItemList(new InternalItemsQuery
+ {
+ IncludeItemTypes = new string[] { typeof(LiveTvProgram).Name },
+ ExternalSeriesId = info.SeriesId,
+ Limit = 1,
+ ImageTypes = new ImageType[] { ImageType.Primary }
+
+ }).FirstOrDefault();
+
+ if (program != null)
+ {
+ var image = program.GetImageInfo(ImageType.Primary, 0);
+ if (image != null)
+ {
+ try
+ {
+ dto.ParentPrimaryImageTag = _imageProcessor.GetImageCacheTag(program, image);
+ dto.ParentPrimaryImageItemId = program.Id.ToString("N");
+ }
+ catch (Exception ex)
+ {
+ }
+ }
+ }
+ }
+
return dto;
}
@@ -244,6 +278,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
PostPaddingSeconds = dto.PostPaddingSeconds,
IsPostPaddingRequired = dto.IsPostPaddingRequired,
IsPrePaddingRequired = dto.IsPrePaddingRequired,
+ KeepUntil = dto.KeepUntil,
Priority = dto.Priority,
SeriesTimerId = dto.ExternalSeriesTimerId,
ProgramId = dto.ExternalProgramId,
@@ -308,6 +343,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv
Priority = dto.Priority,
RecordAnyChannel = dto.RecordAnyChannel,
RecordAnyTime = dto.RecordAnyTime,
+ SkipEpisodesInLibrary = dto.SkipEpisodesInLibrary,
+ KeepUpTo = dto.KeepUpTo,
+ KeepUntil = dto.KeepUntil,
RecordNewOnly = dto.RecordNewOnly,
ProgramId = dto.ExternalProgramId,
ChannelId = dto.ExternalChannelId,
diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
index b3ced55a5..bac9789b5 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
@@ -62,9 +62,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv
private readonly List<ILiveTvService> _services = new List<ILiveTvService>();
- private readonly ConcurrentDictionary<string, LiveStreamData> _openStreams =
- new ConcurrentDictionary<string, LiveStreamData>();
-
private readonly SemaphoreSlim _refreshRecordingsLock = new SemaphoreSlim(1, 1);
private readonly List<ITunerHost> _tunerHosts = new List<ITunerHost>();
@@ -124,9 +121,15 @@ namespace MediaBrowser.Server.Implementations.LiveTv
foreach (var service in _services)
{
service.DataSourceChanged += service_DataSourceChanged;
+ service.RecordingStatusChanged += Service_RecordingStatusChanged;
}
}
+ private void Service_RecordingStatusChanged(object sender, RecordingStatusChangedEventArgs e)
+ {
+ _lastRecordingRefreshTime = DateTime.MinValue;
+ }
+
public List<ITunerHost> TunerHosts
{
get { return _tunerHosts; }
@@ -151,112 +154,40 @@ namespace MediaBrowser.Server.Implementations.LiveTv
var topFolder = await GetInternalLiveTvFolder(cancellationToken).ConfigureAwait(false);
- var channels = _libraryManager.GetItemList(new InternalItemsQuery
+ var internalQuery = new InternalItemsQuery(user)
{
+ IsMovie = query.IsMovie,
+ IsNews = query.IsNews,
+ IsKids = query.IsKids,
+ IsSports = query.IsSports,
+ IsSeries = query.IsSeries,
IncludeItemTypes = new[] { typeof(LiveTvChannel).Name },
- SortBy = new[] { ItemSortBy.SortName },
- TopParentIds = new[] { topFolder.Id.ToString("N") }
+ SortOrder = query.SortOrder ?? SortOrder.Ascending,
+ TopParentIds = new[] { topFolder.Id.ToString("N") },
+ IsFavorite = query.IsFavorite,
+ IsLiked = query.IsLiked,
+ StartIndex = query.StartIndex,
+ Limit = query.Limit
+ };
- }).Cast<LiveTvChannel>();
+ internalQuery.OrderBy.AddRange(query.SortBy.Select(i => new Tuple<string, SortOrder>(i, query.SortOrder ?? SortOrder.Ascending)));
- if (user != null)
+ if (query.EnableFavoriteSorting)
{
- // Avoid implicitly captured closure
- var currentUser = user;
-
- channels = channels
- .Where(i => i.IsVisible(currentUser))
- .OrderBy(i =>
- {
- double number = 0;
-
- if (!string.IsNullOrEmpty(i.Number))
- {
- double.TryParse(i.Number, out number);
- }
-
- return number;
-
- });
-
- if (query.IsFavorite.HasValue)
- {
- var val = query.IsFavorite.Value;
-
- channels = channels
- .Where(i => _userDataManager.GetUserData(user, i).IsFavorite == val);
- }
-
- if (query.IsLiked.HasValue)
- {
- var val = query.IsLiked.Value;
-
- channels = channels
- .Where(i =>
- {
- var likes = _userDataManager.GetUserData(user, i).Likes;
-
- return likes.HasValue && likes.Value == val;
- });
- }
-
- if (query.IsDisliked.HasValue)
- {
- var val = query.IsDisliked.Value;
-
- channels = channels
- .Where(i =>
- {
- var likes = _userDataManager.GetUserData(user, i).Likes;
-
- return likes.HasValue && likes.Value != val;
- });
- }
+ internalQuery.OrderBy.Insert(0, new Tuple<string, SortOrder>(ItemSortBy.IsFavoriteOrLiked, SortOrder.Descending));
}
- var enableFavoriteSorting = query.EnableFavoriteSorting;
-
- channels = channels.OrderBy(i =>
+ if (!internalQuery.OrderBy.Any(i => string.Equals(i.Item1, ItemSortBy.SortName, StringComparison.OrdinalIgnoreCase)))
{
- if (enableFavoriteSorting)
- {
- var userData = _userDataManager.GetUserData(user, i);
-
- if (userData.IsFavorite)
- {
- return 0;
- }
- if (userData.Likes.HasValue)
- {
- if (!userData.Likes.Value)
- {
- return 3;
- }
-
- return 1;
- }
- }
-
- return 2;
- });
-
- var allChannels = channels.ToList();
- IEnumerable<LiveTvChannel> allEnumerable = allChannels;
-
- if (query.StartIndex.HasValue)
- {
- allEnumerable = allEnumerable.Skip(query.StartIndex.Value);
+ internalQuery.OrderBy.Add(new Tuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Ascending));
}
- if (query.Limit.HasValue)
- {
- allEnumerable = allEnumerable.Take(query.Limit.Value);
- }
+ var channelResult = _libraryManager.GetItemsResult(internalQuery);
var result = new QueryResult<LiveTvChannel>
{
- Items = allEnumerable.ToArray(),
- TotalRecordCount = allChannels.Count
+ Items = channelResult.Items.Cast<LiveTvChannel>().ToArray(),
+ TotalRecordCount = channelResult.TotalRecordCount
};
return result;
@@ -298,32 +229,32 @@ namespace MediaBrowser.Server.Implementations.LiveTv
return result.Items.FirstOrDefault();
}
- private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1);
-
public async Task<MediaSourceInfo> GetRecordingStream(string id, CancellationToken cancellationToken)
{
- return await GetLiveStream(id, null, false, cancellationToken).ConfigureAwait(false);
+ var info = await GetLiveStream(id, null, false, cancellationToken).ConfigureAwait(false);
+
+ return info.Item1;
}
- public async Task<MediaSourceInfo> GetChannelStream(string id, string mediaSourceId, CancellationToken cancellationToken)
+ public async Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> GetChannelStream(string id, string mediaSourceId, CancellationToken cancellationToken)
{
return await GetLiveStream(id, mediaSourceId, true, cancellationToken).ConfigureAwait(false);
}
- public async Task<IEnumerable<MediaSourceInfo>> GetRecordingMediaSources(string id, CancellationToken cancellationToken)
+ public async Task<IEnumerable<MediaSourceInfo>> GetRecordingMediaSources(IHasMediaSources item, CancellationToken cancellationToken)
{
- var item = await GetInternalRecording(id, cancellationToken).ConfigureAwait(false);
- var service = GetService(item);
+ var baseItem = (BaseItem)item;
+ var service = GetService(baseItem);
- return await service.GetRecordingStreamMediaSources(id, cancellationToken).ConfigureAwait(false);
+ return await service.GetRecordingStreamMediaSources(baseItem.ExternalId, cancellationToken).ConfigureAwait(false);
}
- public async Task<IEnumerable<MediaSourceInfo>> GetChannelMediaSources(string id, CancellationToken cancellationToken)
+ public async Task<IEnumerable<MediaSourceInfo>> GetChannelMediaSources(IHasMediaSources item, CancellationToken cancellationToken)
{
- var item = GetInternalChannel(id);
- var service = GetService(item);
+ var baseItem = (LiveTvChannel)item;
+ var service = GetService(baseItem);
- var sources = await service.GetChannelStreamMediaSources(item.ExternalId, cancellationToken).ConfigureAwait(false);
+ var sources = await service.GetChannelStreamMediaSources(baseItem.ExternalId, cancellationToken).ConfigureAwait(false);
if (sources.Count == 0)
{
@@ -334,7 +265,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
foreach (var source in list)
{
- Normalize(source, service, item.ChannelType == ChannelType.TV);
+ Normalize(source, service, baseItem.ChannelType == ChannelType.TV);
}
return list;
@@ -355,79 +286,67 @@ namespace MediaBrowser.Server.Implementations.LiveTv
return _services.FirstOrDefault(i => string.Equals(i.Name, name, StringComparison.OrdinalIgnoreCase));
}
- private async Task<MediaSourceInfo> GetLiveStream(string id, string mediaSourceId, bool isChannel, CancellationToken cancellationToken)
+ private async Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> GetLiveStream(string id, string mediaSourceId, bool isChannel, CancellationToken cancellationToken)
{
- await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
-
if (string.Equals(id, mediaSourceId, StringComparison.OrdinalIgnoreCase))
{
mediaSourceId = null;
}
- try
+ MediaSourceInfo info;
+ bool isVideo;
+ ILiveTvService service;
+ IDirectStreamProvider directStreamProvider = null;
+
+ if (isChannel)
{
- MediaSourceInfo info;
- bool isVideo;
- ILiveTvService service;
+ var channel = GetInternalChannel(id);
+ isVideo = channel.ChannelType == ChannelType.TV;
+ service = GetService(channel);
+ _logger.Info("Opening channel stream from {0}, external channel Id: {1}", service.Name, channel.ExternalId);
- if (isChannel)
+ var supportsManagedStream = service as ISupportsDirectStreamProvider;
+ if (supportsManagedStream != null)
{
- var channel = GetInternalChannel(id);
- isVideo = channel.ChannelType == ChannelType.TV;
- service = GetService(channel);
- _logger.Info("Opening channel stream from {0}, external channel Id: {1}", service.Name, channel.ExternalId);
- info = await service.GetChannelStream(channel.ExternalId, mediaSourceId, cancellationToken).ConfigureAwait(false);
- info.RequiresClosing = true;
-
- if (info.RequiresClosing)
- {
- var idPrefix = service.GetType().FullName.GetMD5().ToString("N") + "_";
-
- info.LiveStreamId = idPrefix + info.Id;
- }
+ var streamInfo = await supportsManagedStream.GetChannelStreamWithDirectStreamProvider(channel.ExternalId, mediaSourceId, cancellationToken).ConfigureAwait(false);
+ info = streamInfo.Item1;
+ directStreamProvider = streamInfo.Item2;
}
else
{
- var recording = await GetInternalRecording(id, cancellationToken).ConfigureAwait(false);
- isVideo = !string.Equals(recording.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase);
- service = GetService(recording);
-
- _logger.Info("Opening recording stream from {0}, external recording Id: {1}", service.Name, recording.ExternalId);
- info = await service.GetRecordingStream(recording.ExternalId, null, cancellationToken).ConfigureAwait(false);
- info.RequiresClosing = true;
+ info = await service.GetChannelStream(channel.ExternalId, mediaSourceId, cancellationToken).ConfigureAwait(false);
+ }
+ info.RequiresClosing = true;
- if (info.RequiresClosing)
- {
- var idPrefix = service.GetType().FullName.GetMD5().ToString("N") + "_";
+ if (info.RequiresClosing)
+ {
+ var idPrefix = service.GetType().FullName.GetMD5().ToString("N") + "_";
- info.LiveStreamId = idPrefix + info.Id;
- }
+ info.LiveStreamId = idPrefix + info.Id;
}
+ }
+ else
+ {
+ var recording = await GetInternalRecording(id, cancellationToken).ConfigureAwait(false);
+ isVideo = !string.Equals(recording.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase);
+ service = GetService(recording);
- _logger.Info("Live stream info: {0}", _jsonSerializer.SerializeToString(info));
- Normalize(info, service, isVideo);
+ _logger.Info("Opening recording stream from {0}, external recording Id: {1}", service.Name, recording.ExternalId);
+ info = await service.GetRecordingStream(recording.ExternalId, null, cancellationToken).ConfigureAwait(false);
+ info.RequiresClosing = true;
- var data = new LiveStreamData
+ if (info.RequiresClosing)
{
- Info = info,
- IsChannel = isChannel,
- ItemId = id
- };
-
- _openStreams.AddOrUpdate(info.Id, data, (key, i) => data);
+ var idPrefix = service.GetType().FullName.GetMD5().ToString("N") + "_";
- return info;
+ info.LiveStreamId = idPrefix + info.Id;
+ }
}
- catch (Exception ex)
- {
- _logger.ErrorException("Error getting channel stream", ex);
- throw;
- }
- finally
- {
- _liveStreamSemaphore.Release();
- }
+ _logger.Info("Live stream info: {0}", _jsonSerializer.SerializeToString(info));
+ Normalize(info, service, isVideo);
+
+ return new Tuple<MediaSourceInfo, IDirectStreamProvider>(info, directStreamProvider);
}
private void Normalize(MediaSourceInfo mediaSource, ILiveTvService service, bool isVideo)
@@ -628,11 +547,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv
return item;
}
- private async Task<LiveTvProgram> GetProgram(ProgramInfo info, LiveTvChannel channel, ChannelType channelType, string serviceName, CancellationToken cancellationToken)
+ private Tuple<LiveTvProgram, bool, bool> GetProgram(ProgramInfo info, Dictionary<Guid, LiveTvProgram> allExistingPrograms, LiveTvChannel channel, ChannelType channelType, string serviceName, CancellationToken cancellationToken)
{
var id = _tvDtoService.GetInternalProgramId(serviceName, info.Id);
- var item = _libraryManager.GetItemById(id) as LiveTvProgram;
+ LiveTvProgram item = null;
+ allExistingPrograms.TryGetValue(id, out item);
+
var isNew = false;
var forceUpdate = false;
@@ -649,6 +570,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv
};
}
+ var seriesId = info.SeriesId;
+
if (!item.ParentId.Equals(channel.Id))
{
forceUpdate = true;
@@ -668,6 +591,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv
item.EpisodeTitle = info.EpisodeTitle;
item.ExternalId = info.Id;
+ item.ExternalSeriesIdLegacy = seriesId;
+
+ if (!string.IsNullOrWhiteSpace(seriesId) && !string.Equals(item.ExternalSeriesId, seriesId, StringComparison.Ordinal))
+ {
+ forceUpdate = true;
+ }
+ item.ExternalSeriesId = seriesId;
+
item.Genres = info.Genres;
item.IsHD = info.IsHD;
item.IsKids = info.IsKids;
@@ -698,7 +629,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv
item.HomePageUrl = info.HomePageUrl;
item.ProductionYear = info.ProductionYear;
- item.PremiereDate = info.OriginalAirDate;
+
+ if (!info.IsSeries || info.IsRepeat)
+ {
+ item.PremiereDate = info.OriginalAirDate;
+ }
item.IndexNumber = info.EpisodeNumber;
item.ParentIndexNumber = info.SeasonNumber;
@@ -725,13 +660,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv
}
}
+ var isUpdated = false;
if (isNew)
{
- await _libraryManager.CreateItem(item, cancellationToken).ConfigureAwait(false);
}
else if (forceUpdate || string.IsNullOrWhiteSpace(info.Etag))
{
- await _libraryManager.UpdateItem(item, ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false);
+ isUpdated = true;
}
else
{
@@ -741,13 +676,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv
if (!string.Equals(etag, item.ExternalEtag, StringComparison.OrdinalIgnoreCase))
{
item.ExternalEtag = etag;
- await _libraryManager.UpdateItem(item, ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false);
+ isUpdated = true;
}
}
- _providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(_fileSystem));
-
- return item;
+ return new Tuple<LiveTvProgram, bool, bool>(item, isNew, isUpdated);
}
private async Task<Guid> CreateRecordingRecord(RecordingInfo info, string serviceName, Guid parentFolderId, CancellationToken cancellationToken)
@@ -811,6 +744,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
recording.IsRepeat = info.IsRepeat;
recording.IsSports = info.IsSports;
recording.SeriesTimerId = info.SeriesTimerId;
+ recording.TimerId = info.TimerId;
recording.StartDate = info.StartDate;
if (!dataChanged)
@@ -903,8 +837,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv
var dto = _dtoService.GetBaseItemDto(program, new DtoOptions(), user);
- var list = new List<Tuple<BaseItemDto, string, string>>();
- list.Add(new Tuple<BaseItemDto, string, string>(dto, program.ServiceName, program.ExternalId));
+ var list = new List<Tuple<BaseItemDto, string, string, string>>();
+ list.Add(new Tuple<BaseItemDto, string, string, string>(dto, program.ServiceName, program.ExternalId, program.ExternalSeriesIdLegacy));
await AddRecordingInfo(list, cancellationToken).ConfigureAwait(false);
@@ -932,17 +866,41 @@ namespace MediaBrowser.Server.Implementations.LiveTv
MaxStartDate = query.MaxStartDate,
ChannelIds = query.ChannelIds,
IsMovie = query.IsMovie,
+ IsSeries = query.IsSeries,
IsSports = query.IsSports,
IsKids = query.IsKids,
+ IsNews = query.IsNews,
Genres = query.Genres,
StartIndex = query.StartIndex,
Limit = query.Limit,
SortBy = query.SortBy,
SortOrder = query.SortOrder ?? SortOrder.Ascending,
EnableTotalRecordCount = query.EnableTotalRecordCount,
- TopParentIds = new[] { topFolder.Id.ToString("N") }
+ TopParentIds = new[] { topFolder.Id.ToString("N") },
+ DtoOptions = options
};
+ if (!string.IsNullOrWhiteSpace(query.SeriesTimerId))
+ {
+ var seriesTimers = await GetSeriesTimersInternal(new SeriesTimerQuery { }, cancellationToken).ConfigureAwait(false);
+ var seriesTimer = seriesTimers.Items.FirstOrDefault(i => string.Equals(_tvDtoService.GetInternalSeriesTimerId(i.ServiceName, i.Id).ToString("N"), query.SeriesTimerId, StringComparison.OrdinalIgnoreCase));
+ if (seriesTimer != null)
+ {
+ internalQuery.ExternalSeriesId = seriesTimer.SeriesId;
+
+ if (string.IsNullOrWhiteSpace(seriesTimer.SeriesId))
+ {
+ // Better to return nothing than every program in the database
+ return new QueryResult<BaseItemDto>();
+ }
+ }
+ else
+ {
+ // Better to return nothing than every program in the database
+ return new QueryResult<BaseItemDto>();
+ }
+ }
+
if (query.HasAired.HasValue)
{
if (query.HasAired.Value)
@@ -970,7 +928,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
return result;
}
- public async Task<QueryResult<LiveTvProgram>> GetRecommendedProgramsInternal(RecommendedProgramQuery query, CancellationToken cancellationToken)
+ public async Task<QueryResult<LiveTvProgram>> GetRecommendedProgramsInternal(RecommendedProgramQuery query, DtoOptions options, CancellationToken cancellationToken)
{
var user = _userManager.GetUserById(query.UserId);
@@ -980,12 +938,15 @@ namespace MediaBrowser.Server.Implementations.LiveTv
{
IncludeItemTypes = new[] { typeof(LiveTvProgram).Name },
IsAiring = query.IsAiring,
+ IsNews = query.IsNews,
IsMovie = query.IsMovie,
+ IsSeries = query.IsSeries,
IsSports = query.IsSports,
IsKids = query.IsKids,
EnableTotalRecordCount = query.EnableTotalRecordCount,
SortBy = new[] { ItemSortBy.StartDate },
- TopParentIds = new[] { topFolder.Id.ToString("N") }
+ TopParentIds = new[] { topFolder.Id.ToString("N") },
+ DtoOptions = options
};
if (query.Limit.HasValue)
@@ -1009,9 +970,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv
var programList = programs.ToList();
- var factorChannelWatchCount = (query.IsAiring ?? false) || (query.IsKids ?? false) || (query.IsSports ?? false) || (query.IsMovie ?? false);
+ var factorChannelWatchCount = (query.IsAiring ?? false) || (query.IsKids ?? false) || (query.IsSports ?? false) || (query.IsMovie ?? false) || (query.IsNews ?? false) || (query.IsSeries ?? false);
- programs = programList.OrderBy(i => i.HasImage(ImageType.Primary) ? 0 : 1)
+ programs = programList.OrderBy(i => i.StartDate.Date)
.ThenByDescending(i => GetRecommendationScore(i, user.Id, factorChannelWatchCount))
.ThenBy(i => i.StartDate);
@@ -1035,7 +996,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
public async Task<QueryResult<BaseItemDto>> GetRecommendedPrograms(RecommendedProgramQuery query, DtoOptions options, CancellationToken cancellationToken)
{
- var internalResult = await GetRecommendedProgramsInternal(query, cancellationToken).ConfigureAwait(false);
+ var internalResult = await GetRecommendedProgramsInternal(query, options, cancellationToken).ConfigureAwait(false);
var user = _userManager.GetUserById(query.UserId);
@@ -1092,15 +1053,17 @@ namespace MediaBrowser.Server.Implementations.LiveTv
return score;
}
- private async Task AddRecordingInfo(IEnumerable<Tuple<BaseItemDto, string, string>> programs, CancellationToken cancellationToken)
+ private async Task AddRecordingInfo(IEnumerable<Tuple<BaseItemDto, string, string, string>> programs, CancellationToken cancellationToken)
{
var timers = new Dictionary<string, List<TimerInfo>>();
+ var seriesTimers = new Dictionary<string, List<SeriesTimerInfo>>();
foreach (var programTuple in programs)
{
var program = programTuple.Item1;
var serviceName = programTuple.Item2;
var externalProgramId = programTuple.Item3;
+ string externalSeriesId = programTuple.Item4;
if (string.IsNullOrWhiteSpace(serviceName))
{
@@ -1123,18 +1086,54 @@ namespace MediaBrowser.Server.Implementations.LiveTv
}
var timer = timerList.FirstOrDefault(i => string.Equals(i.ProgramId, externalProgramId, StringComparison.OrdinalIgnoreCase));
+ var foundSeriesTimer = false;
if (timer != null)
{
- program.TimerId = _tvDtoService.GetInternalTimerId(serviceName, timer.Id)
- .ToString("N");
+ if (timer.Status != RecordingStatus.Cancelled && timer.Status != RecordingStatus.Error)
+ {
+ program.TimerId = _tvDtoService.GetInternalTimerId(serviceName, timer.Id)
+ .ToString("N");
+
+ program.Status = timer.Status.ToString();
+ }
if (!string.IsNullOrEmpty(timer.SeriesTimerId))
{
program.SeriesTimerId = _tvDtoService.GetInternalSeriesTimerId(serviceName, timer.SeriesTimerId)
.ToString("N");
+
+ foundSeriesTimer = true;
}
}
+
+ if (foundSeriesTimer || string.IsNullOrWhiteSpace(externalSeriesId))
+ {
+ continue;
+ }
+
+ List<SeriesTimerInfo> seriesTimerList;
+ if (!seriesTimers.TryGetValue(serviceName, out seriesTimerList))
+ {
+ try
+ {
+ var tempTimers = await GetService(serviceName).GetSeriesTimersAsync(cancellationToken).ConfigureAwait(false);
+ seriesTimers[serviceName] = seriesTimerList = tempTimers.ToList();
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error getting series timer infos", ex);
+ seriesTimers[serviceName] = seriesTimerList = new List<SeriesTimerInfo>();
+ }
+ }
+
+ var seriesTimer = seriesTimerList.FirstOrDefault(i => string.Equals(i.SeriesId, externalSeriesId, StringComparison.OrdinalIgnoreCase));
+
+ if (seriesTimer != null)
+ {
+ program.SeriesTimerId = _tvDtoService.GetInternalSeriesTimerId(serviceName, seriesTimer.Id)
+ .ToString("N");
+ }
}
}
@@ -1267,14 +1266,96 @@ namespace MediaBrowser.Server.Implementations.LiveTv
var start = DateTime.UtcNow.AddHours(-1);
var end = start.AddDays(guideDays);
+ var isMovie = false;
+ var isSports = false;
+ var isNews = false;
+ var isKids = false;
+ var iSSeries = false;
+
var channelPrograms = await service.GetProgramsAsync(currentChannel.ExternalId, start, end, cancellationToken).ConfigureAwait(false);
+ var existingPrograms = _libraryManager.GetItemList(new InternalItemsQuery
+ {
+
+ IncludeItemTypes = new string[] { typeof(LiveTvProgram).Name },
+ ChannelIds = new string[] { currentChannel.Id.ToString("N") }
+
+ }).Cast<LiveTvProgram>().ToDictionary(i => i.Id);
+
+ var newPrograms = new List<LiveTvProgram>();
+ var updatedPrograms = new List<LiveTvProgram>();
+
foreach (var program in channelPrograms)
{
- var programItem = await GetProgram(program, currentChannel, currentChannel.ChannelType, service.Name, cancellationToken).ConfigureAwait(false);
+ var programTuple = GetProgram(program, existingPrograms, currentChannel, currentChannel.ChannelType, service.Name, cancellationToken);
+ var programItem = programTuple.Item1;
+
+ if (programTuple.Item2)
+ {
+ newPrograms.Add(programItem);
+ }
+ else if (programTuple.Item3)
+ {
+ updatedPrograms.Add(programItem);
+ }
programs.Add(programItem.Id);
+
+ if (program.IsMovie)
+ {
+ isMovie = true;
+ }
+
+ if (program.IsSeries)
+ {
+ iSSeries = true;
+ }
+
+ if (program.IsSports)
+ {
+ isSports = true;
+ }
+
+ if (program.IsNews)
+ {
+ isNews = true;
+ }
+
+ if (program.IsKids)
+ {
+ isKids = true;
+ }
+ }
+
+ _logger.Debug("Channel {0} has {1} new programs and {2} updated programs", currentChannel.Name, newPrograms.Count, updatedPrograms.Count);
+
+ if (newPrograms.Count > 0)
+ {
+ await _libraryManager.CreateItems(newPrograms, cancellationToken).ConfigureAwait(false);
}
+
+ // TODO: Do this in bulk
+ foreach (var program in updatedPrograms)
+ {
+ await _libraryManager.UpdateItem(program, ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false);
+ }
+
+ foreach (var program in newPrograms)
+ {
+ _providerManager.QueueRefresh(program.Id, new MetadataRefreshOptions(_fileSystem));
+ }
+ foreach (var program in updatedPrograms)
+ {
+ _providerManager.QueueRefresh(program.Id, new MetadataRefreshOptions(_fileSystem));
+ }
+
+ currentChannel.IsMovie = isMovie;
+ currentChannel.IsNews = isNews;
+ currentChannel.IsSports = isSports;
+ currentChannel.IsKids = isKids;
+ currentChannel.IsSeries = iSSeries;
+
+ await currentChannel.UpdateToRepository(ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
@@ -1361,7 +1442,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
private DateTime _lastRecordingRefreshTime;
private async Task RefreshRecordings(CancellationToken cancellationToken)
{
- const int cacheMinutes = 5;
+ const int cacheMinutes = 3;
if ((DateTime.UtcNow - _lastRecordingRefreshTime).TotalMinutes < cacheMinutes)
{
@@ -1409,9 +1490,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv
}
}
- private QueryResult<BaseItem> GetEmbyRecordings(RecordingQuery query, User user)
+ private QueryResult<BaseItem> GetEmbyRecordings(RecordingQuery query, DtoOptions dtoOptions, User user)
{
- if (user == null || (query.IsInProgress ?? false))
+ if (user == null)
+ {
+ return new QueryResult<BaseItem>();
+ }
+
+ if ((query.IsInProgress ?? false))
{
return new QueryResult<BaseItem>();
}
@@ -1485,7 +1571,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv
EnableTotalRecordCount = query.EnableTotalRecordCount,
IncludeItemTypes = includeItemTypes.ToArray(),
ExcludeItemTypes = excludeItemTypes.ToArray(),
- Genres = genres.ToArray()
+ Genres = genres.ToArray(),
+ DtoOptions = dtoOptions
});
}
@@ -1556,9 +1643,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv
return new QueryResult<BaseItem>();
}
- if (_services.Count == 1)
+ if (_services.Count == 1 && !(query.IsInProgress ?? false))
{
- return GetEmbyRecordings(query, user);
+ return GetEmbyRecordings(query, new DtoOptions(), user);
}
await RefreshRecordings(cancellationToken).ConfigureAwait(false);
@@ -1609,6 +1696,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv
recordings = recordings.Where(i => i.IsMovie == val);
}
+ if (query.IsNews.HasValue)
+ {
+ var val = query.IsNews.Value;
+ recordings = recordings.Where(i => i.IsNews == val);
+ }
+
if (query.IsSeries.HasValue)
{
var val = query.IsSeries.Value;
@@ -1659,7 +1752,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
public async Task AddInfoToProgramDto(List<Tuple<BaseItem, BaseItemDto>> tuples, List<ItemFields> fields, User user = null)
{
- var recordingTuples = new List<Tuple<BaseItemDto, string, string>>();
+ var recordingTuples = new List<Tuple<BaseItemDto, string, string, string>>();
foreach (var tuple in tuples)
{
@@ -1727,7 +1820,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
dto.ServiceName = serviceName;
}
- recordingTuples.Add(new Tuple<BaseItemDto, string, string>(dto, serviceName, program.ExternalId));
+ recordingTuples.Add(new Tuple<BaseItemDto, string, string, string>(dto, serviceName, program.ExternalId, program.ExternalSeriesIdLegacy));
}
await AddRecordingInfo(recordingTuples, CancellationToken.None).ConfigureAwait(false);
@@ -1746,6 +1839,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv
? null
: _tvDtoService.GetInternalSeriesTimerId(service.Name, info.SeriesTimerId).ToString("N");
+ dto.TimerId = string.IsNullOrEmpty(info.TimerId)
+ ? null
+ : _tvDtoService.GetInternalTimerId(service.Name, info.TimerId).ToString("N");
+
dto.StartDate = info.StartDate;
dto.RecordingStatus = info.Status;
dto.IsRepeat = info.IsRepeat;
@@ -1842,6 +1939,18 @@ namespace MediaBrowser.Server.Implementations.LiveTv
}
}
+ if (query.IsScheduled.HasValue)
+ {
+ if (query.IsScheduled.Value)
+ {
+ timers = timers.Where(i => i.Item1.Status == RecordingStatus.New);
+ }
+ else
+ {
+ timers = timers.Where(i => !(i.Item1.Status == RecordingStatus.New));
+ }
+ }
+
if (!string.IsNullOrEmpty(query.ChannelId))
{
var guid = new Guid(query.ChannelId);
@@ -2002,6 +2111,56 @@ namespace MediaBrowser.Server.Implementations.LiveTv
return results.Items.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase));
}
+ private async Task<QueryResult<SeriesTimerInfo>> GetSeriesTimersInternal(SeriesTimerQuery query, CancellationToken cancellationToken)
+ {
+ var tasks = _services.Select(async i =>
+ {
+ try
+ {
+ var recs = await i.GetSeriesTimersAsync(cancellationToken).ConfigureAwait(false);
+ return recs.Select(r =>
+ {
+ r.ServiceName = i.Name;
+ return new Tuple<SeriesTimerInfo, ILiveTvService>(r, i);
+ });
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error getting recordings", ex);
+ return new List<Tuple<SeriesTimerInfo, ILiveTvService>>();
+ }
+ });
+ var results = await Task.WhenAll(tasks).ConfigureAwait(false);
+ var timers = results.SelectMany(i => i.ToList());
+
+ if (string.Equals(query.SortBy, "Priority", StringComparison.OrdinalIgnoreCase))
+ {
+ timers = query.SortOrder == SortOrder.Descending ?
+ timers.OrderBy(i => i.Item1.Priority).ThenByStringDescending(i => i.Item1.Name) :
+ timers.OrderByDescending(i => i.Item1.Priority).ThenByString(i => i.Item1.Name);
+ }
+ else
+ {
+ timers = query.SortOrder == SortOrder.Descending ?
+ timers.OrderByStringDescending(i => i.Item1.Name) :
+ timers.OrderByString(i => i.Item1.Name);
+ }
+
+ var returnArray = timers
+ .Select(i =>
+ {
+ return i.Item1;
+
+ })
+ .ToArray();
+
+ return new QueryResult<SeriesTimerInfo>
+ {
+ Items = returnArray,
+ TotalRecordCount = returnArray.Length
+ };
+ }
+
public async Task<QueryResult<SeriesTimerInfoDto>> GetSeriesTimers(SeriesTimerQuery query, CancellationToken cancellationToken)
{
var tasks = _services.Select(async i =>
@@ -2146,6 +2305,19 @@ namespace MediaBrowser.Server.Implementations.LiveTv
var info = await service.GetNewTimerDefaultsAsync(cancellationToken, programInfo).ConfigureAwait(false);
+ info.RecordAnyChannel = true;
+ info.RecordAnyTime = true;
+ info.Days = new List<DayOfWeek>
+ {
+ DayOfWeek.Sunday,
+ DayOfWeek.Monday,
+ DayOfWeek.Tuesday,
+ DayOfWeek.Wednesday,
+ DayOfWeek.Thursday,
+ DayOfWeek.Friday,
+ DayOfWeek.Saturday
+ };
+
info.Id = null;
return new Tuple<SeriesTimerInfo, ILiveTvService>(info, service);
@@ -2399,47 +2571,22 @@ namespace MediaBrowser.Server.Implementations.LiveTv
};
}
- class LiveStreamData
+ public async Task CloseLiveStream(string id)
{
- internal MediaSourceInfo Info;
- internal string ItemId;
- internal bool IsChannel;
- }
+ var parts = id.Split(new[] { '_' }, 2);
- public async Task CloseLiveStream(string id, CancellationToken cancellationToken)
- {
- await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
+ var service = _services.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N"), parts[0], StringComparison.OrdinalIgnoreCase));
- try
+ if (service == null)
{
- var parts = id.Split(new[] { '_' }, 2);
-
- var service = _services.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N"), parts[0], StringComparison.OrdinalIgnoreCase));
-
- if (service == null)
- {
- throw new ArgumentException("Service not found.");
- }
-
- id = parts[1];
-
- LiveStreamData data;
- _openStreams.TryRemove(id, out data);
+ throw new ArgumentException("Service not found.");
+ }
- _logger.Info("Closing live stream from {0}, stream Id: {1}", service.Name, id);
+ id = parts[1];
- await service.CloseLiveStream(id, cancellationToken).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error closing live stream", ex);
+ _logger.Info("Closing live stream from {0}, stream Id: {1}", service.Name, id);
- throw;
- }
- finally
- {
- _liveStreamSemaphore.Release();
- }
+ await service.CloseLiveStream(id, CancellationToken.None).ConfigureAwait(false);
}
public GuideInfo GetGuideInfo()
@@ -2462,7 +2609,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv
Dispose(true);
}
- private readonly object _disposeLock = new object();
private bool _isDisposed = false;
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
@@ -2473,18 +2619,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv
if (dispose)
{
_isDisposed = true;
-
- lock (_disposeLock)
- {
- foreach (var stream in _openStreams.Values.ToList())
- {
- var task = CloseLiveStream(stream.Info.Id, CancellationToken.None);
-
- Task.WaitAll(task);
- }
-
- _openStreams.Clear();
- }
}
}
@@ -2620,7 +2754,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
public async Task<TunerHostInfo> SaveTunerHost(TunerHostInfo info, bool dataSourceChanged = true)
{
- info = (TunerHostInfo)_jsonSerializer.DeserializeFromString(_jsonSerializer.SerializeToString(info), typeof(TunerHostInfo));
+ info = _jsonSerializer.DeserializeFromString<TunerHostInfo>(_jsonSerializer.SerializeToString(info));
var provider = _tunerHosts.FirstOrDefault(i => string.Equals(info.Type, i.Type, StringComparison.OrdinalIgnoreCase));
@@ -2661,7 +2795,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
public async Task<ListingsProviderInfo> SaveListingProvider(ListingsProviderInfo info, bool validateLogin, bool validateListings)
{
- info = (ListingsProviderInfo)_jsonSerializer.DeserializeFromString(_jsonSerializer.SerializeToString(info), typeof(ListingsProviderInfo));
+ info = _jsonSerializer.DeserializeFromString< ListingsProviderInfo>(_jsonSerializer.SerializeToString(info));
var provider = _listingProviders.FirstOrDefault(i => string.Equals(info.Type, i.Type, StringComparison.OrdinalIgnoreCase));
@@ -2803,7 +2937,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
feature = "embytvseriesrecordings";
}
- if (string.Equals(feature, "dvr", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(feature, "dvr-l", StringComparison.OrdinalIgnoreCase))
{
var config = GetConfiguration();
if (config.TunerHosts.Count(i => i.IsEnabled) > 0 &&
diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs
index cdba1873e..a62796036 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs
@@ -9,9 +9,11 @@ using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Serialization;
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Model.Dlna;
namespace MediaBrowser.Server.Implementations.LiveTv
{
@@ -63,12 +65,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv
{
if (item is ILiveTvRecording)
{
- sources = await _liveTvManager.GetRecordingMediaSources(item.Id.ToString("N"), cancellationToken)
+ sources = await _liveTvManager.GetRecordingMediaSources(item, cancellationToken)
.ConfigureAwait(false);
}
else
{
- sources = await _liveTvManager.GetChannelMediaSources(item.Id.ToString("N"), cancellationToken)
+ sources = await _liveTvManager.GetChannelMediaSources(item, cancellationToken)
.ConfigureAwait(false);
}
}
@@ -116,17 +118,20 @@ namespace MediaBrowser.Server.Implementations.LiveTv
return list;
}
- public async Task<MediaSourceInfo> OpenMediaSource(string openToken, CancellationToken cancellationToken)
+ public async Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> OpenMediaSource(string openToken, CancellationToken cancellationToken)
{
- MediaSourceInfo stream;
+ MediaSourceInfo stream = null;
const bool isAudio = false;
var keys = openToken.Split(new[] { StreamIdDelimeter }, 3);
var mediaSourceId = keys.Length >= 3 ? keys[2] : null;
+ IDirectStreamProvider directStreamProvider = null;
if (string.Equals(keys[0], typeof(LiveTvChannel).Name, StringComparison.OrdinalIgnoreCase))
{
- stream = await _liveTvManager.GetChannelStream(keys[1], mediaSourceId, cancellationToken).ConfigureAwait(false);
+ var info = await _liveTvManager.GetChannelStream(keys[1], mediaSourceId, cancellationToken).ConfigureAwait(false);
+ stream = info.Item1;
+ directStreamProvider = info.Item2;
}
else
{
@@ -135,14 +140,21 @@ namespace MediaBrowser.Server.Implementations.LiveTv
try
{
- await AddMediaInfo(stream, isAudio, cancellationToken).ConfigureAwait(false);
+ if (stream.MediaStreams.Any(i => i.Index != -1))
+ {
+ await AddMediaInfo(stream, isAudio, cancellationToken).ConfigureAwait(false);
+ }
+ else
+ {
+ await new LiveStreamHelper(_mediaEncoder, _logger).AddMediaInfoWithProbe(stream, isAudio, cancellationToken).ConfigureAwait(false);
+ }
}
catch (Exception ex)
{
_logger.ErrorException("Error probing live tv stream", ex);
}
- return stream;
+ return new Tuple<MediaSourceInfo, IDirectStreamProvider>(stream, directStreamProvider);
}
private async Task AddMediaInfo(MediaSourceInfo mediaSource, bool isAudio, CancellationToken cancellationToken)
@@ -204,9 +216,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv
}
}
- public Task CloseMediaSource(string liveStreamId, CancellationToken cancellationToken)
+ public Task CloseMediaSource(string liveStreamId)
{
- return _liveTvManager.CloseLiveStream(liveStreamId, cancellationToken);
+ return _liveTvManager.CloseLiveStream(liveStreamId);
}
}
}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
index 9bb5b4fd7..0fe74798f 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
@@ -6,9 +6,11 @@ using MediaBrowser.Model.Logging;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Serialization;
@@ -17,7 +19,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
{
public abstract class BaseTunerHost
{
- protected readonly IConfigurationManager Config;
+ protected readonly IServerConfigurationManager Config;
protected readonly ILogger Logger;
protected IJsonSerializer JsonSerializer;
protected readonly IMediaEncoder MediaEncoder;
@@ -25,7 +27,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
private readonly ConcurrentDictionary<string, ChannelCache> _channelCache =
new ConcurrentDictionary<string, ChannelCache>(StringComparer.OrdinalIgnoreCase);
- protected BaseTunerHost(IConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder)
+ protected BaseTunerHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder)
{
Config = config;
Logger = logger;
@@ -71,7 +73,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
.ToList();
}
- public async Task<IEnumerable<ChannelInfo>> GetChannels(CancellationToken cancellationToken)
+ public async Task<IEnumerable<ChannelInfo>> GetChannels(bool enableCache, CancellationToken cancellationToken)
{
var list = new List<ChannelInfo>();
@@ -81,7 +83,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
{
try
{
- var channels = await GetChannels(host, true, cancellationToken).ConfigureAwait(false);
+ var channels = await GetChannels(host, enableCache, cancellationToken).ConfigureAwait(false);
var newChannels = channels.Where(i => !list.Any(l => string.Equals(i.Id, l.Id, StringComparison.OrdinalIgnoreCase))).ToList();
list.AddRange(newChannels);
@@ -124,12 +126,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
foreach (var host in hostsWithChannel)
{
- var resourcePool = GetLock(host.Url);
- Logger.Debug("GetChannelStreamMediaSources - Waiting on tuner resource pool");
-
- await resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
- Logger.Debug("GetChannelStreamMediaSources - Unlocked resource pool");
-
try
{
// Check to make sure the tuner is available
@@ -155,89 +151,63 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
{
Logger.Error("Error opening tuner", ex);
}
- finally
- {
- resourcePool.Release();
- }
}
}
return new List<MediaSourceInfo>();
}
- protected abstract Task<MediaSourceInfo> GetChannelStream(TunerHostInfo tuner, string channelId, string streamId, CancellationToken cancellationToken);
+ protected abstract Task<LiveStream> GetChannelStream(TunerHostInfo tuner, string channelId, string streamId, CancellationToken cancellationToken);
- public async Task<Tuple<MediaSourceInfo, SemaphoreSlim>> GetChannelStream(string channelId, string streamId, CancellationToken cancellationToken)
+ public async Task<LiveStream> GetChannelStream(string channelId, string streamId, CancellationToken cancellationToken)
{
- if (IsValidChannelId(channelId))
+ if (!IsValidChannelId(channelId))
{
- var hosts = GetTunerHosts();
-
- var hostsWithChannel = new List<TunerHostInfo>();
+ throw new FileNotFoundException();
+ }
- foreach (var host in hosts)
- {
- if (string.IsNullOrWhiteSpace(streamId))
- {
- try
- {
- var channels = await GetChannels(host, true, cancellationToken).ConfigureAwait(false);
+ var hosts = GetTunerHosts();
- if (channels.Any(i => string.Equals(i.Id, channelId, StringComparison.OrdinalIgnoreCase)))
- {
- hostsWithChannel.Add(host);
- }
- }
- catch (Exception ex)
- {
- Logger.Error("Error getting channels", ex);
- }
- }
- else if (streamId.StartsWith(host.Id, StringComparison.OrdinalIgnoreCase))
- {
- hostsWithChannel = new List<TunerHostInfo> { host };
- streamId = streamId.Substring(host.Id.Length);
- break;
- }
- }
+ var hostsWithChannel = new List<TunerHostInfo>();
- foreach (var host in hostsWithChannel)
+ foreach (var host in hosts)
+ {
+ if (string.IsNullOrWhiteSpace(streamId))
{
- var resourcePool = GetLock(host.Url);
- Logger.Debug("GetChannelStream - Waiting on tuner resource pool");
- await resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
- Logger.Debug("GetChannelStream - Unlocked resource pool");
try
{
- // Check to make sure the tuner is available
- // If there's only one tuner, don't bother with the check and just let the tuner be the one to throw an error
- // If a streamId is specified then availibility has already been checked in GetChannelStreamMediaSources
- if (string.IsNullOrWhiteSpace(streamId) && hostsWithChannel.Count > 1)
- {
- if (!await IsAvailable(host, channelId, cancellationToken).ConfigureAwait(false))
- {
- Logger.Error("Tuner is not currently available");
- resourcePool.Release();
- continue;
- }
- }
-
- var stream = await GetChannelStream(host, channelId, streamId, cancellationToken).ConfigureAwait(false);
+ var channels = await GetChannels(host, true, cancellationToken).ConfigureAwait(false);
- if (EnableMediaProbing)
+ if (channels.Any(i => string.Equals(i.Id, channelId, StringComparison.OrdinalIgnoreCase)))
{
- await AddMediaInfo(stream, false, resourcePool, cancellationToken).ConfigureAwait(false);
+ hostsWithChannel.Add(host);
}
-
- return new Tuple<MediaSourceInfo, SemaphoreSlim>(stream, resourcePool);
}
catch (Exception ex)
{
- Logger.Error("Error opening tuner", ex);
-
- resourcePool.Release();
+ Logger.Error("Error getting channels", ex);
}
}
+ else if (streamId.StartsWith(host.Id, StringComparison.OrdinalIgnoreCase))
+ {
+ hostsWithChannel = new List<TunerHostInfo> { host };
+ streamId = streamId.Substring(host.Id.Length);
+ break;
+ }
+ }
+
+ foreach (var host in hostsWithChannel)
+ {
+ try
+ {
+ var liveStream = await GetChannelStream(host, channelId, streamId, cancellationToken).ConfigureAwait(false);
+ await liveStream.Open(cancellationToken).ConfigureAwait(false);
+ return liveStream;
+ }
+ catch (Exception ex)
+ {
+ Logger.Error("Error opening tuner", ex);
+ }
}
throw new LiveTvConflictException();
@@ -263,117 +233,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
protected abstract Task<bool> IsAvailableInternal(TunerHostInfo tuner, string channelId, CancellationToken cancellationToken);
- /// <summary>
- /// The _semaphoreLocks
- /// </summary>
- private readonly ConcurrentDictionary<string, SemaphoreSlim> _semaphoreLocks = new ConcurrentDictionary<string, SemaphoreSlim>(StringComparer.OrdinalIgnoreCase);
- /// <summary>
- /// Gets the lock.
- /// </summary>
- /// <param name="url">The filename.</param>
- /// <returns>System.Object.</returns>
- private SemaphoreSlim GetLock(string url)
- {
- return _semaphoreLocks.GetOrAdd(url, key => new SemaphoreSlim(1, 1));
- }
-
- private async Task AddMediaInfo(MediaSourceInfo mediaSource, bool isAudio, SemaphoreSlim resourcePool, CancellationToken cancellationToken)
- {
- await resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
-
- try
- {
- await AddMediaInfoInternal(mediaSource, isAudio, cancellationToken).ConfigureAwait(false);
-
- // Leave the resource locked. it will be released upstream
- }
- catch (Exception)
- {
- // Release the resource if there's some kind of failure.
- resourcePool.Release();
-
- throw;
- }
- }
-
- private async Task AddMediaInfoInternal(MediaSourceInfo mediaSource, bool isAudio, CancellationToken cancellationToken)
- {
- var originalRuntime = mediaSource.RunTimeTicks;
-
- var info = await MediaEncoder.GetMediaInfo(new MediaInfoRequest
- {
- InputPath = mediaSource.Path,
- Protocol = mediaSource.Protocol,
- MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video,
- ExtractChapters = false
-
- }, cancellationToken).ConfigureAwait(false);
-
- mediaSource.Bitrate = info.Bitrate;
- mediaSource.Container = info.Container;
- mediaSource.Formats = info.Formats;
- mediaSource.MediaStreams = info.MediaStreams;
- mediaSource.RunTimeTicks = info.RunTimeTicks;
- mediaSource.Size = info.Size;
- mediaSource.Timestamp = info.Timestamp;
- mediaSource.Video3DFormat = info.Video3DFormat;
- mediaSource.VideoType = info.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 = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == Model.Entities.MediaStreamType.Audio);
-
- if (audioStream == null || audioStream.Index == -1)
- {
- mediaSource.DefaultAudioStreamIndex = null;
- }
- else
- {
- mediaSource.DefaultAudioStreamIndex = audioStream.Index;
- }
-
- var videoStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == Model.Entities.MediaStreamType.Video);
- if (videoStream != null)
- {
- if (!videoStream.BitRate.HasValue)
- {
- var width = videoStream.Width ?? 1920;
-
- if (width >= 1900)
- {
- videoStream.BitRate = 8000000;
- }
-
- else if (width >= 1260)
- {
- videoStream.BitRate = 3000000;
- }
-
- else if (width >= 700)
- {
- videoStream.BitRate = 1000000;
- }
- }
- }
-
- // Try to estimate this
- if (!mediaSource.Bitrate.HasValue)
- {
- var total = mediaSource.MediaStreams.Select(i => i.BitRate ?? 0).Sum();
-
- if (total > 0)
- {
- mediaSource.Bitrate = total;
- }
- }
- }
-
protected abstract bool IsValidChannelId(string channelId);
protected LiveTvOptions GetConfiguration()
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunDiscovery.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunDiscovery.cs
index 9ba1c60cc..cd168ba58 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunDiscovery.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunDiscovery.cs
@@ -10,6 +10,7 @@ using System;
using System.Linq;
using System.Threading;
using MediaBrowser.Common.Net;
+using MediaBrowser.Model.Events;
using MediaBrowser.Model.Serialization;
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
@@ -39,13 +40,15 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
_deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered;
}
- void _deviceDiscovery_DeviceDiscovered(object sender, SsdpMessageEventArgs e)
+ void _deviceDiscovery_DeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
{
string server = null;
- if (e.Headers.TryGetValue("SERVER", out server) && server.IndexOf("HDHomeRun", StringComparison.OrdinalIgnoreCase) != -1)
+ var info = e.Argument;
+
+ if (info.Headers.TryGetValue("SERVER", out server) && server.IndexOf("HDHomeRun", StringComparison.OrdinalIgnoreCase) != -1)
{
string location;
- if (e.Headers.TryGetValue("Location", out location))
+ if (info.Headers.TryGetValue("Location", out location))
{
//_logger.Debug("HdHomerun found at {0}", location);
@@ -85,7 +88,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
using (var stream = await _httpClient.Get(new HttpRequestOptions
{
Url = string.Format("{0}/discover.json", url),
- CancellationToken = CancellationToken.None
+ CancellationToken = CancellationToken.None,
+ BufferContent = false
}))
{
var response = _json.DeserializeFromStream<HdHomerunHost.DiscoverResponse>(stream);
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
index fd4775938..97d52836d 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
@@ -14,7 +14,10 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using CommonIO;
using MediaBrowser.Common.Extensions;
+using MediaBrowser.Controller;
+using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Net;
@@ -24,11 +27,15 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
public class HdHomerunHost : BaseTunerHost, ITunerHost, IConfigurableTunerHost
{
private readonly IHttpClient _httpClient;
+ private readonly IFileSystem _fileSystem;
+ private readonly IServerApplicationHost _appHost;
- public HdHomerunHost(IConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IHttpClient httpClient)
+ public HdHomerunHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IHttpClient httpClient, IFileSystem fileSystem, IServerApplicationHost appHost)
: base(config, logger, jsonSerializer, mediaEncoder)
{
_httpClient = httpClient;
+ _fileSystem = fileSystem;
+ _appHost = appHost;
}
public string Name
@@ -60,20 +67,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
return id;
}
- public string ApplyDuration(string streamPath, TimeSpan duration)
- {
- streamPath += streamPath.IndexOf('?') == -1 ? "?" : "&";
- streamPath += "duration=" + Convert.ToInt32(duration.TotalSeconds).ToString(CultureInfo.InvariantCulture);
-
- return streamPath;
- }
-
private async Task<IEnumerable<Channels>> GetLineup(TunerHostInfo info, CancellationToken cancellationToken)
{
var options = new HttpRequestOptions
{
Url = string.Format("{0}/lineup.json", GetApiUrl(info, false)),
- CancellationToken = cancellationToken
+ CancellationToken = cancellationToken,
+ BufferContent = false
};
using (var stream = await _httpClient.Get(options))
{
@@ -105,8 +105,18 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
});
}
+ private readonly Dictionary<string, DiscoverResponse> _modelCache = new Dictionary<string, DiscoverResponse>();
private async Task<string> GetModelInfo(TunerHostInfo info, CancellationToken cancellationToken)
{
+ lock (_modelCache)
+ {
+ DiscoverResponse response;
+ if (_modelCache.TryGetValue(info.Url, out response))
+ {
+ return response.ModelNumber;
+ }
+ }
+
try
{
using (var stream = await _httpClient.Get(new HttpRequestOptions()
@@ -115,11 +125,17 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
CancellationToken = cancellationToken,
CacheLength = TimeSpan.FromDays(1),
CacheMode = CacheMode.Unconditional,
- TimeoutMs = Convert.ToInt32(TimeSpan.FromSeconds(5).TotalMilliseconds)
+ TimeoutMs = Convert.ToInt32(TimeSpan.FromSeconds(5).TotalMilliseconds),
+ BufferContent = false
}))
{
var response = JsonSerializer.DeserializeFromStream<DiscoverResponse>(stream);
+ lock (_modelCache)
+ {
+ _modelCache[info.Id] = response;
+ }
+
return response.ModelNumber;
}
}
@@ -127,8 +143,16 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
if (ex.StatusCode.HasValue && ex.StatusCode.Value == System.Net.HttpStatusCode.NotFound)
{
+ var defaultValue = "HDHR";
// HDHR4 doesn't have this api
- return "HDHR";
+ lock (_modelCache)
+ {
+ _modelCache[info.Id] = new DiscoverResponse
+ {
+ ModelNumber = defaultValue
+ };
+ }
+ return defaultValue;
}
throw;
@@ -143,7 +167,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
Url = string.Format("{0}/tuners.html", GetApiUrl(info, false)),
CancellationToken = cancellationToken,
- TimeoutMs = Convert.ToInt32(TimeSpan.FromSeconds(5).TotalMilliseconds)
+ TimeoutMs = Convert.ToInt32(TimeSpan.FromSeconds(5).TotalMilliseconds),
+ BufferContent = false
}))
{
var tuners = new List<LiveTvTunerInfo>();
@@ -319,18 +344,21 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
videoBitrate = 1000000;
}
- if (string.IsNullOrWhiteSpace(videoCodec))
+ var channels = await GetChannels(info, true, CancellationToken.None).ConfigureAwait(false);
+ var channel = channels.FirstOrDefault(i => string.Equals(i.Number, channelId, StringComparison.OrdinalIgnoreCase));
+ if (channel != null)
{
- var channels = await GetChannels(info, true, CancellationToken.None).ConfigureAwait(false);
- var channel = channels.FirstOrDefault(i => string.Equals(i.Number, channelId, StringComparison.OrdinalIgnoreCase));
- if (channel != null)
+ if (string.IsNullOrWhiteSpace(videoCodec))
{
videoCodec = channel.VideoCodec;
- audioCodec = channel.AudioCodec;
+ }
+ audioCodec = channel.AudioCodec;
+ if (!videoBitrate.HasValue)
+ {
videoBitrate = (channel.IsHD ?? true) ? 15000000 : 2000000;
- audioBitrate = (channel.IsHD ?? true) ? 448000 : 192000;
}
+ audioBitrate = (channel.IsHD ?? true) ? 448000 : 192000;
}
// normalize
@@ -352,6 +380,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
url += "?transcode=" + profile;
}
+ var id = profile;
+ if (string.IsNullOrWhiteSpace(id))
+ {
+ id = "native";
+ }
+ id += "_" + url.GetMD5().ToString("N");
+
var mediaSource = new MediaSourceInfo
{
Path = url,
@@ -380,14 +415,15 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
BitRate = audioBitrate
}
},
- RequiresOpening = false,
+ RequiresOpening = true,
RequiresClosing = false,
BufferMs = 0,
Container = "ts",
- Id = profile,
- SupportsDirectPlay = true,
- SupportsDirectStream = false,
- SupportsTranscoding = true
+ Id = id,
+ SupportsDirectPlay = false,
+ SupportsDirectStream = true,
+ SupportsTranscoding = true,
+ IsInfiniteStream = true
};
return mediaSource;
@@ -417,18 +453,21 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
try
{
- string model = await GetModelInfo(info, cancellationToken).ConfigureAwait(false);
- model = model ?? string.Empty;
-
- if (info.AllowHWTranscoding && (model.IndexOf("hdtc", StringComparison.OrdinalIgnoreCase) != -1))
+ if (info.AllowHWTranscoding)
{
- list.Add(await GetMediaSource(info, hdhrId, "heavy").ConfigureAwait(false));
+ string model = await GetModelInfo(info, cancellationToken).ConfigureAwait(false);
+ model = model ?? string.Empty;
- list.Add(await GetMediaSource(info, hdhrId, "internet540").ConfigureAwait(false));
- list.Add(await GetMediaSource(info, hdhrId, "internet480").ConfigureAwait(false));
- list.Add(await GetMediaSource(info, hdhrId, "internet360").ConfigureAwait(false));
- list.Add(await GetMediaSource(info, hdhrId, "internet240").ConfigureAwait(false));
- list.Add(await GetMediaSource(info, hdhrId, "mobile").ConfigureAwait(false));
+ if ((model.IndexOf("hdtc", StringComparison.OrdinalIgnoreCase) != -1))
+ {
+ list.Add(await GetMediaSource(info, hdhrId, "heavy").ConfigureAwait(false));
+
+ list.Add(await GetMediaSource(info, hdhrId, "internet540").ConfigureAwait(false));
+ list.Add(await GetMediaSource(info, hdhrId, "internet480").ConfigureAwait(false));
+ list.Add(await GetMediaSource(info, hdhrId, "internet360").ConfigureAwait(false));
+ list.Add(await GetMediaSource(info, hdhrId, "internet240").ConfigureAwait(false));
+ list.Add(await GetMediaSource(info, hdhrId, "mobile").ConfigureAwait(false));
+ }
}
}
catch
@@ -449,9 +488,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
return channelId.StartsWith(ChannelIdPrefix, StringComparison.OrdinalIgnoreCase);
}
- protected override async Task<MediaSourceInfo> GetChannelStream(TunerHostInfo info, string channelId, string streamId, CancellationToken cancellationToken)
+ protected override async Task<LiveStream> GetChannelStream(TunerHostInfo info, string channelId, string streamId, CancellationToken cancellationToken)
{
- Logger.Info("GetChannelStream: channel id: {0}. stream id: {1}", channelId, streamId ?? string.Empty);
+ var profile = streamId.Split('_')[0];
+
+ Logger.Info("GetChannelStream: channel id: {0}. stream id: {1} profile: {2}", channelId, streamId, profile);
if (!channelId.StartsWith(ChannelIdPrefix, StringComparison.OrdinalIgnoreCase))
{
@@ -459,7 +500,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
}
var hdhrId = GetHdHrIdFromChannelId(channelId);
- return await GetMediaSource(info, hdhrId, streamId).ConfigureAwait(false);
+ var mediaSource = await GetMediaSource(info, hdhrId, profile).ConfigureAwait(false);
+
+ var liveStream = new HdHomerunLiveStream(mediaSource, streamId, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost);
+ liveStream.EnableStreamSharing = true;
+ return liveStream;
}
public async Task Validate(TunerHostInfo info)
@@ -469,13 +514,19 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
return;
}
+ lock (_modelCache)
+ {
+ _modelCache.Clear();
+ }
+
try
{
// Test it by pulling down the lineup
using (var stream = await _httpClient.Get(new HttpRequestOptions
{
Url = string.Format("{0}/discover.json", GetApiUrl(info, false)),
- CancellationToken = CancellationToken.None
+ CancellationToken = CancellationToken.None,
+ BufferContent = false
}))
{
var response = JsonSerializer.DeserializeFromStream<DiscoverResponse>(stream);
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunLiveStream.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunLiveStream.cs
new file mode 100644
index 000000000..60222415c
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunLiveStream.cs
@@ -0,0 +1,145 @@
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using CommonIO;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.MediaInfo;
+using MediaBrowser.Server.Implementations.LiveTv.EmbyTV;
+using System.Collections.Generic;
+using System.Linq;
+using MediaBrowser.Common.Extensions;
+
+namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
+{
+ public class HdHomerunLiveStream : LiveStream, IDirectStreamProvider
+ {
+ private readonly ILogger _logger;
+ private readonly IHttpClient _httpClient;
+ private readonly IFileSystem _fileSystem;
+ private readonly IServerApplicationPaths _appPaths;
+ private readonly IServerApplicationHost _appHost;
+
+ private readonly CancellationTokenSource _liveStreamCancellationTokenSource = new CancellationTokenSource();
+ private readonly TaskCompletionSource<bool> _liveStreamTaskCompletionSource = new TaskCompletionSource<bool>();
+ private readonly MulticastStream _multicastStream;
+
+
+ public HdHomerunLiveStream(MediaSourceInfo mediaSource, string originalStreamId, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost)
+ : base(mediaSource)
+ {
+ _fileSystem = fileSystem;
+ _httpClient = httpClient;
+ _logger = logger;
+ _appPaths = appPaths;
+ _appHost = appHost;
+ OriginalStreamId = originalStreamId;
+ _multicastStream = new MulticastStream(_logger);
+ }
+
+ protected override async Task OpenInternal(CancellationToken openCancellationToken)
+ {
+ _liveStreamCancellationTokenSource.Token.ThrowIfCancellationRequested();
+
+ var mediaSource = OriginalMediaSource;
+
+ var url = mediaSource.Path;
+
+ _logger.Info("Opening HDHR Live stream from {0}", url);
+
+ var taskCompletionSource = new TaskCompletionSource<bool>();
+
+ StartStreaming(url, taskCompletionSource, _liveStreamCancellationTokenSource.Token);
+
+ //OpenedMediaSource.Protocol = MediaProtocol.File;
+ //OpenedMediaSource.Path = tempFile;
+ //OpenedMediaSource.ReadAtNativeFramerate = true;
+
+ OpenedMediaSource.Path = _appHost.GetLocalApiUrl("localhost") + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts";
+ OpenedMediaSource.Protocol = MediaProtocol.Http;
+ OpenedMediaSource.SupportsDirectPlay = false;
+ OpenedMediaSource.SupportsDirectStream = true;
+ OpenedMediaSource.SupportsTranscoding = true;
+
+ await taskCompletionSource.Task.ConfigureAwait(false);
+
+ //await Task.Delay(5000).ConfigureAwait(false);
+ }
+
+ public override Task Close()
+ {
+ _logger.Info("Closing HDHR live stream");
+ _liveStreamCancellationTokenSource.Cancel();
+
+ return _liveStreamTaskCompletionSource.Task;
+ }
+
+ private async Task StartStreaming(string url, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken)
+ {
+ await Task.Run(async () =>
+ {
+ var isFirstAttempt = true;
+
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ try
+ {
+ using (var response = await _httpClient.SendAsync(new HttpRequestOptions
+ {
+ Url = url,
+ CancellationToken = cancellationToken,
+ BufferContent = false
+
+ }, "GET").ConfigureAwait(false))
+ {
+ _logger.Info("Opened HDHR stream from {0}", url);
+
+ if (!cancellationToken.IsCancellationRequested)
+ {
+ _logger.Info("Beginning multicastStream.CopyUntilCancelled");
+
+ Action onStarted = null;
+ if (isFirstAttempt)
+ {
+ onStarted = () => openTaskCompletionSource.TrySetResult(true);
+ }
+
+ await _multicastStream.CopyUntilCancelled(response.Content, onStarted, cancellationToken).ConfigureAwait(false);
+ }
+ }
+ }
+ catch (OperationCanceledException)
+ {
+ break;
+ }
+ catch (Exception ex)
+ {
+ if (isFirstAttempt)
+ {
+ _logger.ErrorException("Error opening live stream:", ex);
+ openTaskCompletionSource.TrySetException(ex);
+ break;
+ }
+
+ _logger.ErrorException("Error copying live stream, will reopen", ex);
+ }
+
+ isFirstAttempt = false;
+ }
+
+ _liveStreamTaskCompletionSource.TrySetResult(true);
+
+ }).ConfigureAwait(false);
+ }
+
+ public Task CopyToAsync(Stream stream, CancellationToken cancellationToken)
+ {
+ return _multicastStream.CopyToAsync(stream);
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
index 5c508aacd..b03feefe4 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
@@ -13,8 +13,10 @@ using System.Threading;
using System.Threading.Tasks;
using CommonIO;
using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Serialization;
+using MediaBrowser.Server.Implementations.LiveTv.EmbyTV;
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
{
@@ -23,7 +25,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
private readonly IFileSystem _fileSystem;
private readonly IHttpClient _httpClient;
- public M3UTunerHost(IConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IHttpClient httpClient)
+ public M3UTunerHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IHttpClient httpClient)
: base(config, logger, jsonSerializer, mediaEncoder)
{
_fileSystem = fileSystem;
@@ -63,11 +65,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
return Task.FromResult(list);
}
- protected override async Task<MediaSourceInfo> GetChannelStream(TunerHostInfo info, string channelId, string streamId, CancellationToken cancellationToken)
+ protected override async Task<LiveStream> GetChannelStream(TunerHostInfo info, string channelId, string streamId, CancellationToken cancellationToken)
{
var sources = await GetChannelStreamMediaSources(info, channelId, cancellationToken).ConfigureAwait(false);
- return sources.First();
+ var liveStream = new LiveStream(sources.First());
+ return liveStream;
}
public async Task Validate(TunerHostInfo info)
@@ -136,7 +139,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
RequiresOpening = false,
RequiresClosing = false,
- ReadAtNativeFramerate = false
+ ReadAtNativeFramerate = false,
+
+ Id = channel.Path.GetMD5().ToString("N"),
+ IsInfiniteStream = true
};
return new List<MediaSourceInfo> { mediaSource };
@@ -148,10 +154,5 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
{
return Task.FromResult(true);
}
-
- public string ApplyDuration(string streamPath, TimeSpan duration)
- {
- return streamPath;
- }
}
}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs
new file mode 100644
index 000000000..8ff3fd6c1
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs
@@ -0,0 +1,96 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Model.Logging;
+
+namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
+{
+ public class MulticastStream
+ {
+ private readonly List<QueueStream> _outputStreams = new List<QueueStream>();
+ private const int BufferSize = 81920;
+ private CancellationToken _cancellationToken;
+ private readonly ILogger _logger;
+
+ public MulticastStream(ILogger logger)
+ {
+ _logger = logger;
+ }
+
+ public async Task CopyUntilCancelled(Stream source, Action onStarted, CancellationToken cancellationToken)
+ {
+ _cancellationToken = cancellationToken;
+
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ byte[] buffer = new byte[BufferSize];
+
+ var bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
+
+ if (bytesRead > 0)
+ {
+ byte[] copy = new byte[bytesRead];
+ Buffer.BlockCopy(buffer, 0, copy, 0, bytesRead);
+
+ List<QueueStream> streams = null;
+
+ lock (_outputStreams)
+ {
+ streams = _outputStreams.ToList();
+ }
+
+ foreach (var stream in streams)
+ {
+ stream.Queue(copy);
+ }
+
+ if (onStarted != null)
+ {
+ var onStartedCopy = onStarted;
+ onStarted = null;
+ Task.Run(onStartedCopy);
+ }
+ }
+
+ else
+ {
+ await Task.Delay(100).ConfigureAwait(false);
+ }
+ }
+ }
+
+ public Task CopyToAsync(Stream stream)
+ {
+ var result = new QueueStream(stream, _logger)
+ {
+ OnFinished = OnFinished
+ };
+
+ lock (_outputStreams)
+ {
+ _outputStreams.Add(result);
+ }
+
+ result.Start(_cancellationToken);
+
+ return result.TaskCompletion.Task;
+ }
+
+ public void RemoveOutputStream(QueueStream stream)
+ {
+ lock (_outputStreams)
+ {
+ _outputStreams.Remove(stream);
+ }
+ }
+
+ private void OnFinished(QueueStream queueStream)
+ {
+ RemoveOutputStream(queueStream);
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/QueueStream.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/QueueStream.cs
new file mode 100644
index 000000000..c1566b900
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/QueueStream.cs
@@ -0,0 +1,93 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Model.Logging;
+
+namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
+{
+ public class QueueStream
+ {
+ private readonly Stream _outputStream;
+ private readonly ConcurrentQueue<byte[]> _queue = new ConcurrentQueue<byte[]>();
+ private CancellationToken _cancellationToken;
+ public TaskCompletionSource<bool> TaskCompletion { get; private set; }
+
+ public Action<QueueStream> OnFinished { get; set; }
+ private readonly ILogger _logger;
+
+ public QueueStream(Stream outputStream, ILogger logger)
+ {
+ _outputStream = outputStream;
+ _logger = logger;
+ TaskCompletion = new TaskCompletionSource<bool>();
+ }
+
+ public void Queue(byte[] bytes)
+ {
+ _queue.Enqueue(bytes);
+ }
+
+ public void Start(CancellationToken cancellationToken)
+ {
+ _cancellationToken = cancellationToken;
+ Task.Run(() => StartInternal());
+ }
+
+ private byte[] Dequeue()
+ {
+ byte[] bytes;
+ if (_queue.TryDequeue(out bytes))
+ {
+ return bytes;
+ }
+
+ return null;
+ }
+
+ private async Task StartInternal()
+ {
+ var cancellationToken = _cancellationToken;
+
+ try
+ {
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ var bytes = Dequeue();
+ if (bytes != null)
+ {
+ await _outputStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken).ConfigureAwait(false);
+ }
+ else
+ {
+ await Task.Delay(50, cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ TaskCompletion.TrySetResult(true);
+ _logger.Debug("QueueStream complete");
+ }
+ catch (OperationCanceledException)
+ {
+ _logger.Debug("QueueStream cancelled");
+ TaskCompletion.TrySetCanceled();
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error in QueueStream", ex);
+ TaskCompletion.TrySetException(ex);
+ }
+ finally
+ {
+ if (OnFinished != null)
+ {
+ OnFinished(this);
+ }
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs
index cb0e573da..a0b8ef5f7 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs
@@ -14,6 +14,7 @@ using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Extensions;
using System.Xml.Linq;
+using MediaBrowser.Model.Events;
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
{
@@ -50,18 +51,20 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
_deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered;
}
- void _deviceDiscovery_DeviceDiscovered(object sender, SsdpMessageEventArgs e)
+ void _deviceDiscovery_DeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
{
+ var info = e.Argument;
+
string st = null;
string nt = null;
- e.Headers.TryGetValue("ST", out st);
- e.Headers.TryGetValue("NT", out nt);
+ info.Headers.TryGetValue("ST", out st);
+ info.Headers.TryGetValue("NT", out nt);
if (string.Equals(st, "urn:ses-com:device:SatIPServer:1", StringComparison.OrdinalIgnoreCase) ||
string.Equals(nt, "urn:ses-com:device:SatIPServer:1", StringComparison.OrdinalIgnoreCase))
{
string location;
- if (e.Headers.TryGetValue("Location", out location) && !string.IsNullOrWhiteSpace(location))
+ if (info.Headers.TryGetValue("Location", out location) && !string.IsNullOrWhiteSpace(location))
{
_logger.Debug("SAT IP found at {0}", location);
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpHost.cs
index b1e349a86..81deb2995 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpHost.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpHost.cs
@@ -8,6 +8,7 @@ using CommonIO;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Dto;
@@ -16,6 +17,7 @@ using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Serialization;
+using MediaBrowser.Server.Implementations.LiveTv.EmbyTV;
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
{
@@ -24,7 +26,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
private readonly IFileSystem _fileSystem;
private readonly IHttpClient _httpClient;
- public SatIpHost(IConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IHttpClient httpClient)
+ public SatIpHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IHttpClient httpClient)
: base(config, logger, jsonSerializer, mediaEncoder)
{
_fileSystem = fileSystem;
@@ -113,11 +115,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
return new List<MediaSourceInfo>();
}
- protected override async Task<MediaSourceInfo> GetChannelStream(TunerHostInfo tuner, string channelId, string streamId, CancellationToken cancellationToken)
+ protected override async Task<LiveStream> GetChannelStream(TunerHostInfo tuner, string channelId, string streamId, CancellationToken cancellationToken)
{
var sources = await GetChannelStreamMediaSources(tuner, channelId, cancellationToken).ConfigureAwait(false);
- return sources.First();
+ var liveStream = new LiveStream(sources.First());
+
+ return liveStream;
}
protected override async Task<bool> IsAvailableInternal(TunerHostInfo tuner, string channelId, CancellationToken cancellationToken)
diff --git a/MediaBrowser.Server.Implementations/Localization/Core/en-US.json b/MediaBrowser.Server.Implementations/Localization/Core/en-US.json
index 5e2f98c09..bc0dc236d 100644
--- a/MediaBrowser.Server.Implementations/Localization/Core/en-US.json
+++ b/MediaBrowser.Server.Implementations/Localization/Core/en-US.json
@@ -47,7 +47,6 @@
"NotificationOptionTaskFailed": "Scheduled task failure",
"NotificationOptionInstallationFailed": "Installation failure",
"NotificationOptionNewLibraryContent": "New content added",
- "NotificationOptionNewLibraryContentMultiple": "New content added (multiple)",
"NotificationOptionCameraImageUploaded": "Camera image uploaded",
"NotificationOptionUserLockedOut": "User locked out",
"NotificationOptionServerRestartRequired": "Server restart required",
diff --git a/MediaBrowser.Server.Implementations/Localization/Ratings/de.txt b/MediaBrowser.Server.Implementations/Localization/Ratings/de.txt
index 723807509..ad1f18619 100644
--- a/MediaBrowser.Server.Implementations/Localization/Ratings/de.txt
+++ b/MediaBrowser.Server.Implementations/Localization/Ratings/de.txt
@@ -1,5 +1,10 @@
DE-0,1
+FSK-0,1
DE-6,5
+FSK-6,5
DE-12,7
+FSK-12,7
DE-16,8
+FSK-16,8
DE-18,9
+FSK-18,9 \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
index 8850f3d35..f01a107df 100644
--- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
+++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@@ -11,9 +11,10 @@
<AssemblyName>MediaBrowser.Server.Implementations</AssemblyName>
<FileAlignment>512</FileAlignment>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
- <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ <TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
<ReleaseVersion>
</ReleaseVersion>
+ <TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@@ -69,12 +70,12 @@
<Reference Include="ServiceStack.Api.Swagger">
<HintPath>..\ThirdParty\ServiceStack\ServiceStack.Api.Swagger.dll</HintPath>
</Reference>
- <Reference Include="SimpleInjector, Version=3.2.0.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL">
- <HintPath>..\packages\SimpleInjector.3.2.0\lib\net45\SimpleInjector.dll</HintPath>
+ <Reference Include="SimpleInjector, Version=3.2.2.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL">
+ <HintPath>..\packages\SimpleInjector.3.2.2\lib\net45\SimpleInjector.dll</HintPath>
<Private>True</Private>
</Reference>
- <Reference Include="SocketHttpListener, Version=1.0.6063.4624, Culture=neutral, processorArchitecture=MSIL">
- <HintPath>..\packages\SocketHttpListener.1.0.0.39\lib\net45\SocketHttpListener.dll</HintPath>
+ <Reference Include="SocketHttpListener, Version=1.0.6109.26162, Culture=neutral, processorArchitecture=MSIL">
+ <HintPath>..\packages\SocketHttpListener.1.0.0.40\lib\net45\SocketHttpListener.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
@@ -105,9 +106,6 @@
<Reference Include="UniversalDetector">
<HintPath>..\ThirdParty\UniversalDetector\UniversalDetector.dll</HintPath>
</Reference>
- <Reference Include="Mono.Nat">
- <HintPath>..\packages\Mono.Nat.1.2.24.0\lib\net40\Mono.Nat.dll</HintPath>
- </Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="..\SharedVersion.cs">
@@ -164,7 +162,6 @@
<Compile Include="HttpServer\HttpListenerHost.cs" />
<Compile Include="HttpServer\HttpResultFactory.cs" />
<Compile Include="HttpServer\LoggerUtils.cs" />
- <Compile Include="HttpServer\NativeWebSocket.cs" />
<Compile Include="HttpServer\RangeRequestWriter.cs" />
<Compile Include="HttpServer\ResponseFilter.cs" />
<Compile Include="HttpServer\Security\AuthService.cs" />
@@ -237,6 +234,7 @@
<Compile Include="LiveTv\EmbyTV\TimerManager.cs" />
<Compile Include="LiveTv\Listings\SchedulesDirect.cs" />
<Compile Include="LiveTv\Listings\XmlTvListingsProvider.cs" />
+ <Compile Include="LiveTv\LiveStreamHelper.cs" />
<Compile Include="LiveTv\LiveTvConfigurationFactory.cs" />
<Compile Include="LiveTv\LiveTvDtoService.cs" />
<Compile Include="LiveTv\LiveTvManager.cs" />
@@ -244,11 +242,14 @@
<Compile Include="LiveTv\TunerHosts\BaseTunerHost.cs" />
<Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunHost.cs" />
<Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunDiscovery.cs" />
+ <Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunLiveStream.cs" />
<Compile Include="LiveTv\TunerHosts\M3uParser.cs" />
<Compile Include="LiveTv\TunerHosts\M3UTunerHost.cs" />
<Compile Include="LiveTv\ProgramImageProvider.cs" />
<Compile Include="LiveTv\RecordingImageProvider.cs" />
<Compile Include="LiveTv\RefreshChannelsScheduledTask.cs" />
+ <Compile Include="LiveTv\TunerHosts\MulticastStream.cs" />
+ <Compile Include="LiveTv\TunerHosts\QueueStream.cs" />
<Compile Include="LiveTv\TunerHosts\SatIp\ChannelScan.cs" />
<Compile Include="LiveTv\TunerHosts\SatIp\Rtcp\ReportBlock.cs" />
<Compile Include="LiveTv\TunerHosts\SatIp\Rtcp\RtcpAppPacket.cs" />
@@ -390,6 +391,10 @@
<Project>{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}</Project>
<Name>MediaBrowser.Model</Name>
</ProjectReference>
+ <ProjectReference Include="..\Mono.Nat\Mono.Nat.csproj">
+ <Project>{d7453b88-2266-4805-b39b-2b5a2a33e1ba}</Project>
+ <Name>Mono.Nat</Name>
+ </ProjectReference>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Localization\Ratings\us.txt" />
diff --git a/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs b/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs
index 7d0841fa6..21e847c68 100644
--- a/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs
+++ b/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs
@@ -61,7 +61,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
}
var libraryOptions = _libraryManager.GetLibraryOptions(video);
- if (libraryOptions != null && libraryOptions.SchemaVersion >= 2)
+ if (libraryOptions != null)
{
if (!libraryOptions.EnableChapterImageExtraction)
{
@@ -70,29 +70,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
}
else
{
- var options = _chapterManager.GetConfiguration();
-
- if (video is Movie)
- {
- if (!options.EnableMovieChapterImageExtraction)
- {
- return false;
- }
- }
- else if (video is Episode)
- {
- if (!options.EnableEpisodeChapterImageExtraction)
- {
- return false;
- }
- }
- else
- {
- if (!options.EnableOtherVideoChapterImageExtraction)
- {
- return false;
- }
- }
+ return false;
}
// Can't extract images if there are no video streams
@@ -160,7 +138,9 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
_fileSystem.CreateDirectory(Path.GetDirectoryName(path));
- var tempFile = await _encoder.ExtractVideoImage(inputPath, protocol, video.Video3DFormat, time, cancellationToken).ConfigureAwait(false);
+ var container = video.Container;
+
+ var tempFile = await _encoder.ExtractVideoImage(inputPath, container, protocol, video.Video3DFormat, time, cancellationToken).ConfigureAwait(false);
File.Copy(tempFile, path, true);
try
diff --git a/MediaBrowser.Server.Implementations/News/NewsEntryPoint.cs b/MediaBrowser.Server.Implementations/News/NewsEntryPoint.cs
index 969541fbc..c15af6f70 100644
--- a/MediaBrowser.Server.Implementations/News/NewsEntryPoint.cs
+++ b/MediaBrowser.Server.Implementations/News/NewsEntryPoint.cs
@@ -81,7 +81,8 @@ namespace MediaBrowser.Server.Implementations.News
{
Url = "http://emby.media/community/index.php?/blog/rss/1-media-browser-developers-blog",
Progress = new Progress<double>(),
- UserAgent = "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.42 Safari/537.36"
+ UserAgent = "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.42 Safari/537.36",
+ BufferContent = false
};
using (var stream = await _httpClient.Get(requestOptions).ConfigureAwait(false))
diff --git a/MediaBrowser.Server.Implementations/Notifications/CoreNotificationTypes.cs b/MediaBrowser.Server.Implementations/Notifications/CoreNotificationTypes.cs
index 98d3672fa..8ca667739 100644
--- a/MediaBrowser.Server.Implementations/Notifications/CoreNotificationTypes.cs
+++ b/MediaBrowser.Server.Implementations/Notifications/CoreNotificationTypes.cs
@@ -91,13 +91,6 @@ namespace MediaBrowser.Server.Implementations.Notifications
new NotificationTypeInfo
{
- Type = NotificationType.NewLibraryContentMultiple.ToString(),
- DefaultTitle = "{ItemCount} new items have been added to your media library.",
- Variables = new List<string>{"ItemCount"}
- },
-
- new NotificationTypeInfo
- {
Type = NotificationType.AudioPlayback.ToString(),
DefaultTitle = "{UserName} is playing {ItemName} on {DeviceName}.",
Variables = new List<string>{"UserName", "ItemName", "DeviceName", "AppName"}
diff --git a/MediaBrowser.Server.Implementations/Persistence/DataExtensions.cs b/MediaBrowser.Server.Implementations/Persistence/DataExtensions.cs
index a16d23700..028465354 100644
--- a/MediaBrowser.Server.Implementations/Persistence/DataExtensions.cs
+++ b/MediaBrowser.Server.Implementations/Persistence/DataExtensions.cs
@@ -4,6 +4,7 @@ using MediaBrowser.Model.Serialization;
using System;
using System.Data;
using System.IO;
+using MediaBrowser.Common.IO;
namespace MediaBrowser.Server.Implementations.Persistence
{
@@ -51,18 +52,16 @@ namespace MediaBrowser.Server.Implementations.Persistence
/// <summary>
/// Gets a stream from a DataReader at a given ordinal
/// </summary>
- /// <param name="reader">The reader.</param>
- /// <param name="ordinal">The ordinal.</param>
/// <returns>Stream.</returns>
/// <exception cref="System.ArgumentNullException">reader</exception>
- public static Stream GetMemoryStream(this IDataReader reader, int ordinal)
+ public static Stream GetMemoryStream(this IDataReader reader, int ordinal, IMemoryStreamProvider streamProvider)
{
if (reader == null)
{
throw new ArgumentNullException("reader");
}
- var memoryStream = new MemoryStream();
+ var memoryStream = streamProvider.CreateNew();
var num = 0L;
var array = new byte[4096];
long bytes;
@@ -132,18 +131,16 @@ namespace MediaBrowser.Server.Implementations.Persistence
/// <summary>
/// Serializes to bytes.
/// </summary>
- /// <param name="json">The json.</param>
- /// <param name="obj">The obj.</param>
/// <returns>System.Byte[][].</returns>
/// <exception cref="System.ArgumentNullException">obj</exception>
- public static byte[] SerializeToBytes(this IJsonSerializer json, object obj)
+ public static byte[] SerializeToBytes(this IJsonSerializer json, object obj, IMemoryStreamProvider streamProvider)
{
if (obj == null)
{
throw new ArgumentNullException("obj");
}
- using (var stream = new MemoryStream())
+ using (var stream = streamProvider.CreateNew())
{
json.SerializeToStream(obj, stream);
return stream.ToArray();
diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteDisplayPreferencesRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteDisplayPreferencesRepository.cs
index 40970dbe4..1726a77a6 100644
--- a/MediaBrowser.Server.Implementations/Persistence/SqliteDisplayPreferencesRepository.cs
+++ b/MediaBrowser.Server.Implementations/Persistence/SqliteDisplayPreferencesRepository.cs
@@ -10,6 +10,7 @@ using System.Data;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Common.IO;
namespace MediaBrowser.Server.Implementations.Persistence
{
@@ -18,10 +19,13 @@ namespace MediaBrowser.Server.Implementations.Persistence
/// </summary>
public class SqliteDisplayPreferencesRepository : BaseSqliteRepository, IDisplayPreferencesRepository
{
- public SqliteDisplayPreferencesRepository(ILogManager logManager, IJsonSerializer jsonSerializer, IApplicationPaths appPaths, IDbConnector dbConnector)
+ private readonly IMemoryStreamProvider _memoryStreamProvider;
+
+ public SqliteDisplayPreferencesRepository(ILogManager logManager, IJsonSerializer jsonSerializer, IApplicationPaths appPaths, IDbConnector dbConnector, IMemoryStreamProvider memoryStreamProvider)
: base(logManager, dbConnector)
{
_jsonSerializer = jsonSerializer;
+ _memoryStreamProvider = memoryStreamProvider;
DbFilePath = Path.Combine(appPaths.DataPath, "displaypreferences.db");
}
@@ -82,7 +86,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
cancellationToken.ThrowIfCancellationRequested();
- var serialized = _jsonSerializer.SerializeToBytes(displayPreferences);
+ var serialized = _jsonSerializer.SerializeToBytes(displayPreferences, _memoryStreamProvider);
using (var connection = await CreateConnection().ConfigureAwait(false))
{
@@ -166,7 +170,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
foreach (var displayPreference in displayPreferences)
{
- var serialized = _jsonSerializer.SerializeToBytes(displayPreference);
+ var serialized = _jsonSerializer.SerializeToBytes(displayPreference, _memoryStreamProvider);
using (var cmd = connection.CreateCommand())
{
@@ -246,7 +250,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
{
if (reader.Read())
{
- using (var stream = reader.GetMemoryStream(0))
+ using (var stream = reader.GetMemoryStream(0, _memoryStreamProvider))
{
return _jsonSerializer.DeserializeFromStream<DisplayPreferences>(stream);
}
@@ -283,7 +287,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
{
while (reader.Read())
{
- using (var stream = reader.GetMemoryStream(0))
+ using (var stream = reader.GetMemoryStream(0, _memoryStreamProvider))
{
list.Add(_jsonSerializer.DeserializeFromStream<DisplayPreferences>(stream));
}
diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs
index 5ece3dd82..2235bfe0d 100644
--- a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs
+++ b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs
@@ -19,11 +19,15 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Channels;
+using MediaBrowser.Controller.Collections;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Playlists;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.LiveTv;
+using MediaBrowser.Server.Implementations.Devices;
+using MediaBrowser.Server.Implementations.Playlists;
namespace MediaBrowser.Server.Implementations.Persistence
{
@@ -95,11 +99,12 @@ namespace MediaBrowser.Server.Implementations.Persistence
private IDbCommand _updateInheritedTagsCommand;
public const int LatestSchemaVersion = 109;
+ private readonly IMemoryStreamProvider _memoryStreamProvider;
/// <summary>
/// Initializes a new instance of the <see cref="SqliteItemRepository"/> class.
/// </summary>
- public SqliteItemRepository(IServerConfigurationManager config, IJsonSerializer jsonSerializer, ILogManager logManager, IDbConnector connector)
+ public SqliteItemRepository(IServerConfigurationManager config, IJsonSerializer jsonSerializer, ILogManager logManager, IDbConnector connector, IMemoryStreamProvider memoryStreamProvider)
: base(logManager, connector)
{
if (config == null)
@@ -113,6 +118,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
_config = config;
_jsonSerializer = jsonSerializer;
+ _memoryStreamProvider = memoryStreamProvider;
_criticReviewsPath = Path.Combine(_config.ApplicationPaths.DataPath, "critic-reviews");
DbFilePath = Path.Combine(_config.ApplicationPaths.DataPath, "library.db");
@@ -125,7 +131,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
var cacheSize = _config.Configuration.SqliteCacheSize;
if (cacheSize <= 0)
{
- cacheSize = Math.Min(Environment.ProcessorCount * 50000, 200000);
+ cacheSize = Math.Min(Environment.ProcessorCount * 50000, 100000);
}
var connection = await DbConnector.Connect(DbFilePath, false, false, 0 - cacheSize).ConfigureAwait(false);
@@ -270,6 +276,15 @@ namespace MediaBrowser.Server.Implementations.Persistence
_connection.AddColumn(Logger, "TypedBaseItems", "SeasonId", "GUID");
_connection.AddColumn(Logger, "TypedBaseItems", "SeriesId", "GUID");
_connection.AddColumn(Logger, "TypedBaseItems", "SeriesSortName", "Text");
+ _connection.AddColumn(Logger, "TypedBaseItems", "ExternalSeriesId", "Text");
+ _connection.AddColumn(Logger, "TypedBaseItems", "ShortOverview", "Text");
+ _connection.AddColumn(Logger, "TypedBaseItems", "Tagline", "Text");
+ _connection.AddColumn(Logger, "TypedBaseItems", "Keywords", "Text");
+ _connection.AddColumn(Logger, "TypedBaseItems", "ProviderIds", "Text");
+ _connection.AddColumn(Logger, "TypedBaseItems", "Images", "Text");
+ _connection.AddColumn(Logger, "TypedBaseItems", "ProductionLocations", "Text");
+ _connection.AddColumn(Logger, "TypedBaseItems", "ThemeSongIds", "Text");
+ _connection.AddColumn(Logger, "TypedBaseItems", "ThemeVideoIds", "Text");
_connection.AddColumn(Logger, "UserDataKeys", "Priority", "INT");
_connection.AddColumn(Logger, "ItemValues", "CleanValue", "Text");
@@ -412,7 +427,17 @@ namespace MediaBrowser.Server.Implementations.Persistence
"SeriesId",
"SeriesSortName",
"PresentationUniqueKey",
- "InheritedParentalRatingValue"
+ "InheritedParentalRatingValue",
+ "InheritedTags",
+ "ExternalSeriesId",
+ "ShortOverview",
+ "Tagline",
+ "Keywords",
+ "ProviderIds",
+ "Images",
+ "ProductionLocations",
+ "ThemeSongIds",
+ "ThemeVideoIds"
};
private readonly string[] _mediaStreamSaveColumns =
@@ -534,7 +559,16 @@ namespace MediaBrowser.Server.Implementations.Persistence
"SeasonName",
"SeasonId",
"SeriesId",
- "SeriesSortName"
+ "SeriesSortName",
+ "ExternalSeriesId",
+ "ShortOverview",
+ "Tagline",
+ "Keywords",
+ "ProviderIds",
+ "Images",
+ "ProductionLocations",
+ "ThemeSongIds",
+ "ThemeVideoIds"
};
_saveItemCommand = _connection.CreateCommand();
_saveItemCommand.CommandText = "replace into TypedBaseItems (" + string.Join(",", saveColumns.ToArray()) + ") values (";
@@ -720,7 +754,15 @@ namespace MediaBrowser.Server.Implementations.Persistence
_saveItemCommand.GetParameter(index++).Value = item.Id;
_saveItemCommand.GetParameter(index++).Value = item.GetType().FullName;
- _saveItemCommand.GetParameter(index++).Value = _jsonSerializer.SerializeToBytes(item);
+
+ if (TypeRequiresDeserialization(item.GetType()))
+ {
+ _saveItemCommand.GetParameter(index++).Value = _jsonSerializer.SerializeToBytes(item, _memoryStreamProvider);
+ }
+ else
+ {
+ _saveItemCommand.GetParameter(index++).Value = null;
+ }
_saveItemCommand.GetParameter(index++).Value = item.Path;
@@ -974,6 +1016,49 @@ namespace MediaBrowser.Server.Implementations.Persistence
_saveItemCommand.GetParameter(index++).Value = null;
}
+ _saveItemCommand.GetParameter(index++).Value = item.ExternalSeriesId;
+ _saveItemCommand.GetParameter(index++).Value = item.ShortOverview;
+ _saveItemCommand.GetParameter(index++).Value = item.Tagline;
+
+ if (item.Keywords.Count > 0)
+ {
+ _saveItemCommand.GetParameter(index++).Value = string.Join("|", item.Keywords.ToArray());
+ }
+ else
+ {
+ _saveItemCommand.GetParameter(index++).Value = null;
+ }
+
+ _saveItemCommand.GetParameter(index++).Value = SerializeProviderIds(item);
+ _saveItemCommand.GetParameter(index++).Value = SerializeImages(item);
+
+ if (item.ProductionLocations.Count > 0)
+ {
+ _saveItemCommand.GetParameter(index++).Value = string.Join("|", item.ProductionLocations.ToArray());
+ }
+ else
+ {
+ _saveItemCommand.GetParameter(index++).Value = null;
+ }
+
+ if (item.ThemeSongIds.Count > 0)
+ {
+ _saveItemCommand.GetParameter(index++).Value = string.Join("|", item.ThemeSongIds.Select(i => i.ToString("N")).ToArray());
+ }
+ else
+ {
+ _saveItemCommand.GetParameter(index++).Value = null;
+ }
+
+ if (item.ThemeVideoIds.Count > 0)
+ {
+ _saveItemCommand.GetParameter(index++).Value = string.Join("|", item.ThemeVideoIds.Select(i => i.ToString("N")).ToArray());
+ }
+ else
+ {
+ _saveItemCommand.GetParameter(index++).Value = null;
+ }
+
_saveItemCommand.Transaction = transaction;
_saveItemCommand.ExecuteNonQuery();
@@ -1022,6 +1107,99 @@ namespace MediaBrowser.Server.Implementations.Persistence
}
}
+ private string SerializeProviderIds(BaseItem item)
+ {
+ var ids = item.ProviderIds.ToList();
+
+ if (ids.Count == 0)
+ {
+ return null;
+ }
+
+ return string.Join("|", ids.Select(i => i.Key + "=" + i.Value).ToArray());
+ }
+
+ private void DeserializeProviderIds(string value, BaseItem item)
+ {
+ if (string.IsNullOrWhiteSpace(value))
+ {
+ return;
+ }
+
+ if (item.ProviderIds.Count > 0)
+ {
+ return;
+ }
+
+ var parts = value.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
+
+ foreach (var part in parts)
+ {
+ var idParts = part.Split('=');
+
+ item.SetProviderId(idParts[0], idParts[1]);
+ }
+ }
+
+ private string SerializeImages(BaseItem item)
+ {
+ var images = item.ImageInfos.ToList();
+
+ if (images.Count == 0)
+ {
+ return null;
+ }
+
+ return string.Join("|", images.Select(ToValueString).ToArray());
+ }
+
+ private void DeserializeImages(string value, BaseItem item)
+ {
+ if (string.IsNullOrWhiteSpace(value))
+ {
+ return;
+ }
+
+ if (item.ImageInfos.Count > 0)
+ {
+ return;
+ }
+
+ var parts = value.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
+
+ foreach (var part in parts)
+ {
+ item.ImageInfos.Add(ItemImageInfoFromValueString(part));
+ }
+ }
+
+ public string ToValueString(ItemImageInfo image)
+ {
+ var delimeter = "*";
+
+ return (image.Path ?? string.Empty) +
+ delimeter +
+ image.DateModified.Ticks.ToString(CultureInfo.InvariantCulture) +
+ delimeter +
+ image.Type +
+ delimeter +
+ image.IsPlaceholder;
+ }
+
+ public ItemImageInfo ItemImageInfoFromValueString(string value)
+ {
+ var parts = value.Split(new[] { '*' }, StringSplitOptions.RemoveEmptyEntries);
+
+ var image = new ItemImageInfo();
+
+ image.Path = parts[0];
+ image.DateModified = new DateTime(long.Parse(parts[1], CultureInfo.InvariantCulture), DateTimeKind.Utc);
+ image.Type = (ImageType)Enum.Parse(typeof(ImageType), parts[2], true);
+ image.IsPlaceholder = string.Equals(parts[3], true.ToString(), StringComparison.OrdinalIgnoreCase);
+
+ return image;
+ }
+
/// <summary>
/// Internal retrieve from items or users table
/// </summary>
@@ -1056,6 +1234,91 @@ namespace MediaBrowser.Server.Implementations.Persistence
private BaseItem GetItem(IDataReader reader)
{
+ return GetItem(reader, new InternalItemsQuery());
+ }
+
+ private bool TypeRequiresDeserialization(Type type)
+ {
+ if (_config.Configuration.SkipDeserializationForBasicTypes)
+ {
+ if (type == typeof(MusicGenre))
+ {
+ return false;
+ }
+ if (type == typeof(GameGenre))
+ {
+ return false;
+ }
+ if (type == typeof(Genre))
+ {
+ return false;
+ }
+ if (type == typeof(Studio))
+ {
+ return false;
+ }
+ if (type == typeof(Year))
+ {
+ return false;
+ }
+ if (type == typeof(Book))
+ {
+ return false;
+ }
+ if (type == typeof(Person))
+ {
+ return false;
+ }
+ if (type == typeof(RecordingGroup))
+ {
+ return false;
+ }
+ if (type == typeof(Channel))
+ {
+ return false;
+ }
+ if (type == typeof(ManualCollectionsFolder))
+ {
+ return false;
+ }
+ if (type == typeof(CameraUploadsFolder))
+ {
+ return false;
+ }
+ if (type == typeof(PlaylistsFolder))
+ {
+ return false;
+ }
+ if (type == typeof(UserRootFolder))
+ {
+ return false;
+ }
+ if (type == typeof(PhotoAlbum))
+ {
+ return false;
+ }
+ if (type == typeof(Season))
+ {
+ return false;
+ }
+ if (type == typeof(MusicArtist))
+ {
+ return false;
+ }
+ }
+ if (_config.Configuration.SkipDeserializationForPrograms)
+ {
+ if (type == typeof(LiveTvProgram))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private BaseItem GetItem(IDataReader reader, InternalItemsQuery query)
+ {
var typeString = reader.GetString(0);
var type = _typeMapper.GetType(typeString);
@@ -1069,34 +1332,37 @@ namespace MediaBrowser.Server.Implementations.Persistence
BaseItem item = null;
- using (var stream = reader.GetMemoryStream(1))
+ if (TypeRequiresDeserialization(type))
{
- try
- {
- item = _jsonSerializer.DeserializeFromStream(stream, type) as BaseItem;
- }
- catch (SerializationException ex)
- {
- Logger.ErrorException("Error deserializing item", ex);
- }
-
- if (item == null)
+ using (var stream = reader.GetMemoryStream(1, _memoryStreamProvider))
{
try
{
- item = Activator.CreateInstance(type) as BaseItem;
+ item = _jsonSerializer.DeserializeFromStream(stream, type) as BaseItem;
}
- catch
+ catch (SerializationException ex)
{
+ Logger.ErrorException("Error deserializing item", ex);
}
}
+ }
- if (item == null)
+ if (item == null)
+ {
+ try
+ {
+ item = Activator.CreateInstance(type) as BaseItem;
+ }
+ catch
{
- return null;
}
}
+ if (item == null)
+ {
+ return null;
+ }
+
if (!reader.IsDBNull(2))
{
var hasStartDate = item as IHasStartDate;
@@ -1170,194 +1436,275 @@ namespace MediaBrowser.Server.Implementations.Persistence
}
}
- if (!reader.IsDBNull(15))
- {
- item.CommunityRating = reader.GetFloat(15);
- }
+ var index = 15;
- if (!reader.IsDBNull(16))
+ if (!reader.IsDBNull(index))
{
- item.CustomRating = reader.GetString(16);
+ item.CommunityRating = reader.GetFloat(index);
}
+ index++;
- if (!reader.IsDBNull(17))
+ if (query.HasField(ItemFields.CustomRating))
{
- item.IndexNumber = reader.GetInt32(17);
+ if (!reader.IsDBNull(index))
+ {
+ item.CustomRating = reader.GetString(index);
+ }
+ index++;
}
- if (!reader.IsDBNull(18))
+ if (!reader.IsDBNull(index))
{
- item.IsLocked = reader.GetBoolean(18);
+ item.IndexNumber = reader.GetInt32(index);
}
+ index++;
- if (!reader.IsDBNull(19))
+ if (query.HasField(ItemFields.Settings))
{
- item.PreferredMetadataLanguage = reader.GetString(19);
- }
+ if (!reader.IsDBNull(index))
+ {
+ item.IsLocked = reader.GetBoolean(index);
+ }
+ index++;
- if (!reader.IsDBNull(20))
- {
- item.PreferredMetadataCountryCode = reader.GetString(20);
+ if (!reader.IsDBNull(index))
+ {
+ item.PreferredMetadataLanguage = reader.GetString(index);
+ }
+ index++;
+
+ if (!reader.IsDBNull(index))
+ {
+ item.PreferredMetadataCountryCode = reader.GetString(index);
+ }
+ index++;
}
- if (!reader.IsDBNull(21))
+ if (!reader.IsDBNull(index))
{
- item.IsHD = reader.GetBoolean(21);
+ item.IsHD = reader.GetBoolean(index);
}
+ index++;
- if (!reader.IsDBNull(22))
+ if (!reader.IsDBNull(index))
{
- item.ExternalEtag = reader.GetString(22);
+ item.ExternalEtag = reader.GetString(index);
}
+ index++;
- if (!reader.IsDBNull(23))
+ if (!reader.IsDBNull(index))
{
- item.DateLastRefreshed = reader.GetDateTime(23).ToUniversalTime();
+ item.DateLastRefreshed = reader.GetDateTime(index).ToUniversalTime();
}
+ index++;
- if (!reader.IsDBNull(24))
+ if (!reader.IsDBNull(index))
{
- item.Name = reader.GetString(24);
+ item.Name = reader.GetString(index);
}
+ index++;
- if (!reader.IsDBNull(25))
+ if (!reader.IsDBNull(index))
{
- item.Path = reader.GetString(25);
+ item.Path = reader.GetString(index);
}
+ index++;
- if (!reader.IsDBNull(26))
+ if (!reader.IsDBNull(index))
{
- item.PremiereDate = reader.GetDateTime(26).ToUniversalTime();
+ item.PremiereDate = reader.GetDateTime(index).ToUniversalTime();
}
+ index++;
- if (!reader.IsDBNull(27))
+ if (query.HasField(ItemFields.Overview))
{
- item.Overview = reader.GetString(27);
+ if (!reader.IsDBNull(index))
+ {
+ item.Overview = reader.GetString(index);
+ }
+ index++;
}
- if (!reader.IsDBNull(28))
+ if (!reader.IsDBNull(index))
{
- item.ParentIndexNumber = reader.GetInt32(28);
+ item.ParentIndexNumber = reader.GetInt32(index);
}
+ index++;
- if (!reader.IsDBNull(29))
+ if (!reader.IsDBNull(index))
{
- item.ProductionYear = reader.GetInt32(29);
+ item.ProductionYear = reader.GetInt32(index);
}
+ index++;
- if (!reader.IsDBNull(30))
+ if (!reader.IsDBNull(index))
{
- item.OfficialRating = reader.GetString(30);
+ item.OfficialRating = reader.GetString(index);
}
+ index++;
- if (!reader.IsDBNull(31))
+ if (query.HasField(ItemFields.OfficialRatingDescription))
{
- item.OfficialRatingDescription = reader.GetString(31);
+ if (!reader.IsDBNull(index))
+ {
+ item.OfficialRatingDescription = reader.GetString(index);
+ }
+ index++;
}
- if (!reader.IsDBNull(32))
+ if (query.HasField(ItemFields.HomePageUrl))
{
- item.HomePageUrl = reader.GetString(32);
+ if (!reader.IsDBNull(index))
+ {
+ item.HomePageUrl = reader.GetString(index);
+ }
+ index++;
}
- if (!reader.IsDBNull(33))
+ if (query.HasField(ItemFields.DisplayMediaType))
{
- item.DisplayMediaType = reader.GetString(33);
+ if (!reader.IsDBNull(index))
+ {
+ item.DisplayMediaType = reader.GetString(index);
+ }
+ index++;
}
- if (!reader.IsDBNull(34))
+ if (query.HasField(ItemFields.SortName))
{
- item.ForcedSortName = reader.GetString(34);
+ if (!reader.IsDBNull(index))
+ {
+ item.ForcedSortName = reader.GetString(index);
+ }
+ index++;
}
- if (!reader.IsDBNull(35))
+ if (!reader.IsDBNull(index))
{
- item.RunTimeTicks = reader.GetInt64(35);
+ item.RunTimeTicks = reader.GetInt64(index);
}
+ index++;
- if (!reader.IsDBNull(36))
+ if (query.HasField(ItemFields.VoteCount))
{
- item.VoteCount = reader.GetInt32(36);
+ if (!reader.IsDBNull(index))
+ {
+ item.VoteCount = reader.GetInt32(index);
+ }
+ index++;
}
- if (!reader.IsDBNull(37))
+ if (query.HasField(ItemFields.DateCreated))
{
- item.DateCreated = reader.GetDateTime(37).ToUniversalTime();
+ if (!reader.IsDBNull(index))
+ {
+ item.DateCreated = reader.GetDateTime(index).ToUniversalTime();
+ }
+ index++;
}
- if (!reader.IsDBNull(38))
+ if (!reader.IsDBNull(index))
{
- item.DateModified = reader.GetDateTime(38).ToUniversalTime();
+ item.DateModified = reader.GetDateTime(index).ToUniversalTime();
}
+ index++;
- item.Id = reader.GetGuid(39);
+ item.Id = reader.GetGuid(index);
+ index++;
- if (!reader.IsDBNull(40))
+ if (query.HasField(ItemFields.Genres))
{
- item.Genres = reader.GetString(40).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
+ if (!reader.IsDBNull(index))
+ {
+ item.Genres = reader.GetString(index).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
+ }
+ index++;
}
- if (!reader.IsDBNull(41))
+ if (!reader.IsDBNull(index))
{
- item.ParentId = reader.GetGuid(41);
+ item.ParentId = reader.GetGuid(index);
}
+ index++;
- if (!reader.IsDBNull(42))
+ if (!reader.IsDBNull(index))
{
- item.Audio = (ProgramAudio)Enum.Parse(typeof(ProgramAudio), reader.GetString(42), true);
+ item.Audio = (ProgramAudio)Enum.Parse(typeof(ProgramAudio), reader.GetString(index), true);
}
+ index++;
- if (!reader.IsDBNull(43))
+ if (query.HasField(ItemFields.ServiceName))
{
- item.ServiceName = reader.GetString(43);
+ if (!reader.IsDBNull(index))
+ {
+ item.ServiceName = reader.GetString(index);
+ }
+ index++;
}
- if (!reader.IsDBNull(44))
+ if (!reader.IsDBNull(index))
{
- item.IsInMixedFolder = reader.GetBoolean(44);
+ item.IsInMixedFolder = reader.GetBoolean(index);
}
+ index++;
- if (!reader.IsDBNull(45))
+ if (!reader.IsDBNull(index))
{
- item.DateLastSaved = reader.GetDateTime(45).ToUniversalTime();
+ item.DateLastSaved = reader.GetDateTime(index).ToUniversalTime();
}
+ index++;
- if (!reader.IsDBNull(46))
+ if (query.HasField(ItemFields.Settings))
{
- item.LockedFields = reader.GetString(46).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).Select(i => (MetadataFields)Enum.Parse(typeof(MetadataFields), i, true)).ToList();
+ if (!reader.IsDBNull(index))
+ {
+ item.LockedFields = reader.GetString(index).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).Select(i => (MetadataFields)Enum.Parse(typeof(MetadataFields), i, true)).ToList();
+ }
+ index++;
}
- if (!reader.IsDBNull(47))
+ if (query.HasField(ItemFields.Studios))
{
- item.Studios = reader.GetString(47).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
+ if (!reader.IsDBNull(index))
+ {
+ item.Studios = reader.GetString(index).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
+ }
+ index++;
}
- if (!reader.IsDBNull(48))
+ if (query.HasField(ItemFields.Tags))
{
- item.Tags = reader.GetString(48).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
+ if (!reader.IsDBNull(index))
+ {
+ item.Tags = reader.GetString(index).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
+ }
+ index++;
}
- if (!reader.IsDBNull(49))
+ if (!reader.IsDBNull(index))
{
- item.SourceType = (SourceType)Enum.Parse(typeof(SourceType), reader.GetString(49), true);
+ item.SourceType = (SourceType)Enum.Parse(typeof(SourceType), reader.GetString(index), true);
}
+ index++;
var trailer = item as Trailer;
if (trailer != null)
{
- if (!reader.IsDBNull(50))
+ if (!reader.IsDBNull(index))
{
- trailer.TrailerTypes = reader.GetString(50).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).Select(i => (TrailerType)Enum.Parse(typeof(TrailerType), i, true)).ToList();
+ trailer.TrailerTypes = reader.GetString(index).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).Select(i => (TrailerType)Enum.Parse(typeof(TrailerType), i, true)).ToList();
}
}
+ index++;
- var index = 51;
-
- if (!reader.IsDBNull(index))
+ if (query.HasField(ItemFields.OriginalTitle))
{
- item.OriginalTitle = reader.GetString(index);
+ if (!reader.IsDBNull(index))
+ {
+ item.OriginalTitle = reader.GetString(index);
+ }
+ index++;
}
- index++;
var video = item as Video;
if (video != null)
@@ -1369,12 +1716,15 @@ namespace MediaBrowser.Server.Implementations.Persistence
}
index++;
- var folder = item as Folder;
- if (folder != null && !reader.IsDBNull(index))
+ if (query.HasField(ItemFields.DateLastMediaAdded))
{
- folder.DateLastMediaAdded = reader.GetDateTime(index).ToUniversalTime();
+ var folder = item as Folder;
+ if (folder != null && !reader.IsDBNull(index))
+ {
+ folder.DateLastMediaAdded = reader.GetDateTime(index).ToUniversalTime();
+ }
+ index++;
}
- index++;
if (!reader.IsDBNull(index))
{
@@ -1388,11 +1738,14 @@ namespace MediaBrowser.Server.Implementations.Persistence
}
index++;
- if (!reader.IsDBNull(index))
+ if (query.HasField(ItemFields.CriticRatingSummary))
{
- item.CriticRatingSummary = reader.GetString(index);
+ if (!reader.IsDBNull(index))
+ {
+ item.CriticRatingSummary = reader.GetString(index);
+ }
+ index++;
}
- index++;
if (!reader.IsDBNull(index))
{
@@ -1459,6 +1812,105 @@ namespace MediaBrowser.Server.Implementations.Persistence
}
index++;
+ if (!reader.IsDBNull(index))
+ {
+ item.InheritedTags = reader.GetString(index).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
+ }
+ index++;
+
+ if (!reader.IsDBNull(index))
+ {
+ item.ExternalSeriesId = reader.GetString(index);
+ }
+ index++;
+
+ if (query.HasField(ItemFields.ShortOverview))
+ {
+ if (!reader.IsDBNull(index))
+ {
+ item.ShortOverview = reader.GetString(index);
+ }
+ index++;
+ }
+
+ if (query.HasField(ItemFields.Taglines))
+ {
+ if (!reader.IsDBNull(index))
+ {
+ item.Tagline = reader.GetString(index);
+ }
+ index++;
+ }
+
+ if (query.HasField(ItemFields.Keywords))
+ {
+ if (!reader.IsDBNull(index))
+ {
+ item.Keywords = reader.GetString(index).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
+ }
+ index++;
+ }
+
+ if (!reader.IsDBNull(index))
+ {
+ DeserializeProviderIds(reader.GetString(index), item);
+ }
+ index++;
+
+ if (query.DtoOptions.EnableImages)
+ {
+ if (!reader.IsDBNull(index))
+ {
+ DeserializeImages(reader.GetString(index), item);
+ }
+ index++;
+ }
+
+ if (query.HasField(ItemFields.ProductionLocations))
+ {
+ if (!reader.IsDBNull(index))
+ {
+ item.ProductionLocations = reader.GetString(index).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
+ }
+ index++;
+ }
+
+ if (query.HasField(ItemFields.ThemeSongIds))
+ {
+ if (!reader.IsDBNull(index))
+ {
+ item.ThemeSongIds = reader.GetString(index).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).Select(i => new Guid(i)).ToList();
+ }
+ index++;
+ }
+
+ if (query.HasField(ItemFields.ThemeVideoIds))
+ {
+ if (!reader.IsDBNull(index))
+ {
+ item.ThemeVideoIds = reader.GetString(index).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).Select(i => new Guid(i)).ToList();
+ }
+ index++;
+ }
+
+ if (string.IsNullOrWhiteSpace(item.Tagline))
+ {
+ var movie = item as Movie;
+ if (movie != null && movie.Taglines.Count > 0)
+ {
+ movie.Tagline = movie.Taglines[0];
+ }
+ }
+
+ if (type == typeof(Person) && item.ProductionLocations.Count == 0)
+ {
+ var person = (Person)item;
+ if (!string.IsNullOrWhiteSpace(person.PlaceOfBirth))
+ {
+ item.ProductionLocations = new List<string> { person.PlaceOfBirth };
+ }
+ }
+
return item;
}
@@ -1723,28 +2175,28 @@ namespace MediaBrowser.Server.Implementations.Persistence
return true;
}
- if (query.SortBy != null && query.SortBy.Length > 0)
+ var sortingFields = query.SortBy.ToList();
+ sortingFields.AddRange(query.OrderBy.Select(i => i.Item1));
+
+ if (sortingFields.Contains(ItemSortBy.IsFavoriteOrLiked, StringComparer.OrdinalIgnoreCase))
{
- if (query.SortBy.Contains(ItemSortBy.IsFavoriteOrLiked, StringComparer.OrdinalIgnoreCase))
- {
- return true;
- }
- if (query.SortBy.Contains(ItemSortBy.IsPlayed, StringComparer.OrdinalIgnoreCase))
- {
- return true;
- }
- if (query.SortBy.Contains(ItemSortBy.IsUnplayed, StringComparer.OrdinalIgnoreCase))
- {
- return true;
- }
- if (query.SortBy.Contains(ItemSortBy.PlayCount, StringComparer.OrdinalIgnoreCase))
- {
- return true;
- }
- if (query.SortBy.Contains(ItemSortBy.DatePlayed, StringComparer.OrdinalIgnoreCase))
- {
- return true;
- }
+ return true;
+ }
+ if (sortingFields.Contains(ItemSortBy.IsPlayed, StringComparer.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+ if (sortingFields.Contains(ItemSortBy.IsUnplayed, StringComparer.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+ if (sortingFields.Contains(ItemSortBy.PlayCount, StringComparer.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+ if (sortingFields.Contains(ItemSortBy.DatePlayed, StringComparer.OrdinalIgnoreCase))
+ {
+ return true;
}
if (query.IsFavoriteOrLiked.HasValue)
@@ -1775,10 +2227,52 @@ namespace MediaBrowser.Server.Implementations.Persistence
return false;
}
+ private List<ItemFields> allFields = Enum.GetNames(typeof(ItemFields))
+ .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true))
+ .ToList();
+
+ private IEnumerable<string> GetColumnNamesFromField(ItemFields field)
+ {
+ if (field == ItemFields.Settings)
+ {
+ return new[] { "IsLocked", "PreferredMetadataCountryCode", "PreferredMetadataLanguage", "LockedFields" };
+ }
+ if (field == ItemFields.ServiceName)
+ {
+ return new[] { "ExternalServiceId" };
+ }
+ if (field == ItemFields.SortName)
+ {
+ return new[] { "ForcedSortName" };
+ }
+ if (field == ItemFields.Taglines)
+ {
+ return new[] { "Tagline" };
+ }
+
+ return new[] { field.ToString() };
+ }
+
private string[] GetFinalColumnsToSelect(InternalItemsQuery query, string[] startColumns, IDbCommand cmd)
{
var list = startColumns.ToList();
+ foreach (var field in allFields)
+ {
+ if (!query.HasField(field))
+ {
+ foreach (var fieldToRemove in GetColumnNamesFromField(field).ToList())
+ {
+ list.Remove(fieldToRemove);
+ }
+ }
+ }
+
+ if (!query.DtoOptions.EnableImages)
+ {
+ list.Remove("Images");
+ }
+
if (EnableJoinUserData(query))
{
list.Add("UserDataDb.UserData.UserId");
@@ -1933,7 +2427,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
while (reader.Read())
{
- var item = GetItem(reader);
+ var item = GetItem(reader, query);
if (item != null)
{
list.Add(item);
@@ -2120,7 +2614,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
{
while (reader.Read())
{
- var item = GetItem(reader);
+ var item = GetItem(reader, query);
if (item != null)
{
list.Add(item);
@@ -2144,34 +2638,41 @@ namespace MediaBrowser.Server.Implementations.Persistence
private string GetOrderByText(InternalItemsQuery query)
{
+ var orderBy = query.OrderBy.ToList();
+ var enableOrderInversion = true;
+
+ if (orderBy.Count == 0)
+ {
+ orderBy.AddRange(query.SortBy.Select(i => new Tuple<string, SortOrder>(i, query.SortOrder)));
+ }
+ else
+ {
+ enableOrderInversion = false;
+ }
+
if (query.SimilarTo != null)
{
- if (query.SortBy == null || query.SortBy.Length == 0)
+ if (orderBy.Count == 0)
{
- if (query.User != null)
- {
- query.SortBy = new[] { "SimilarityScore", ItemSortBy.Random };
- }
- else
- {
- query.SortBy = new[] { "SimilarityScore", ItemSortBy.Random };
- }
+ orderBy.Add(new Tuple<string, SortOrder>("SimilarityScore", SortOrder.Descending));
+ orderBy.Add(new Tuple<string, SortOrder>(ItemSortBy.Random, SortOrder.Ascending));
query.SortOrder = SortOrder.Descending;
+ enableOrderInversion = false;
}
}
- if (query.SortBy == null || query.SortBy.Length == 0)
+ query.OrderBy = orderBy;
+
+ if (orderBy.Count == 0)
{
return string.Empty;
}
- var isAscending = query.SortOrder != SortOrder.Descending;
-
- return " ORDER BY " + string.Join(",", query.SortBy.Select(i =>
+ return " ORDER BY " + string.Join(",", orderBy.Select(i =>
{
- var columnMap = MapOrderByField(i, query);
- var columnAscending = isAscending;
- if (columnMap.Item2)
+ var columnMap = MapOrderByField(i.Item1, query);
+ var columnAscending = i.Item2 == SortOrder.Ascending;
+ if (columnMap.Item2 && enableOrderInversion)
{
columnAscending = !columnAscending;
}
@@ -2523,38 +3024,116 @@ namespace MediaBrowser.Server.Implementations.Persistence
whereClauses.Add("IsOffline=@IsOffline");
cmd.Parameters.Add(cmd, "@IsOffline", DbType.Boolean).Value = query.IsOffline;
}
- if (query.IsMovie.HasValue)
+
+ var exclusiveProgramAttribtues = !(query.IsMovie ?? true) ||
+ !(query.IsSports ?? true) ||
+ !(query.IsKids ?? true) ||
+ !(query.IsNews ?? true) ||
+ !(query.IsSeries ?? true);
+
+ if (exclusiveProgramAttribtues)
{
- var alternateTypes = new List<string>();
- if (query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(typeof(Movie).Name))
+ if (query.IsMovie.HasValue)
+ {
+ var alternateTypes = new List<string>();
+ if (query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(typeof(Movie).Name))
+ {
+ alternateTypes.Add(typeof(Movie).FullName);
+ }
+ if (query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(typeof(Trailer).Name))
+ {
+ alternateTypes.Add(typeof(Trailer).FullName);
+ }
+
+ if (alternateTypes.Count == 0)
+ {
+ whereClauses.Add("IsMovie=@IsMovie");
+ cmd.Parameters.Add(cmd, "@IsMovie", DbType.Boolean).Value = query.IsMovie;
+ }
+ else
+ {
+ whereClauses.Add("(IsMovie is null OR IsMovie=@IsMovie)");
+ cmd.Parameters.Add(cmd, "@IsMovie", DbType.Boolean).Value = query.IsMovie;
+ }
+ }
+ if (query.IsSeries.HasValue)
{
- alternateTypes.Add(typeof(Movie).FullName);
+ whereClauses.Add("IsSeries=@IsSeries");
+ cmd.Parameters.Add(cmd, "@IsSeries", DbType.Boolean).Value = query.IsSeries;
}
- if (query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(typeof(Trailer).Name))
+ if (query.IsNews.HasValue)
{
- alternateTypes.Add(typeof(Trailer).FullName);
+ whereClauses.Add("IsNews=@IsNews");
+ cmd.Parameters.Add(cmd, "@IsNews", DbType.Boolean).Value = query.IsNews;
}
-
- if (alternateTypes.Count == 0)
+ if (query.IsKids.HasValue)
{
- whereClauses.Add("IsMovie=@IsMovie");
+ whereClauses.Add("IsKids=@IsKids");
+ cmd.Parameters.Add(cmd, "@IsKids", DbType.Boolean).Value = query.IsKids;
}
- else
+ if (query.IsSports.HasValue)
{
- whereClauses.Add("(IsMovie is null OR IsMovie=@IsMovie)");
+ whereClauses.Add("IsSports=@IsSports");
+ cmd.Parameters.Add(cmd, "@IsSports", DbType.Boolean).Value = query.IsSports;
}
- cmd.Parameters.Add(cmd, "@IsMovie", DbType.Boolean).Value = query.IsMovie;
}
- if (query.IsKids.HasValue)
+ else
{
- whereClauses.Add("IsKids=@IsKids");
- cmd.Parameters.Add(cmd, "@IsKids", DbType.Boolean).Value = query.IsKids;
+ var programAttribtues = new List<string>();
+ if (query.IsMovie ?? false)
+ {
+ var alternateTypes = new List<string>();
+ if (query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(typeof(Movie).Name))
+ {
+ alternateTypes.Add(typeof(Movie).FullName);
+ }
+ if (query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(typeof(Trailer).Name))
+ {
+ alternateTypes.Add(typeof(Trailer).FullName);
+ }
+
+ if (alternateTypes.Count == 0)
+ {
+ programAttribtues.Add("IsMovie=@IsMovie");
+ }
+ else
+ {
+ programAttribtues.Add("(IsMovie is null OR IsMovie=@IsMovie)");
+ }
+
+ cmd.Parameters.Add(cmd, "@IsMovie", DbType.Boolean).Value = true;
+ }
+ if (query.IsSports ?? false)
+ {
+ programAttribtues.Add("IsSports=@IsSports");
+ cmd.Parameters.Add(cmd, "@IsSports", DbType.Boolean).Value = true;
+ }
+ if (query.IsNews ?? false)
+ {
+ programAttribtues.Add("IsNews=@IsNews");
+ cmd.Parameters.Add(cmd, "@IsNews", DbType.Boolean).Value = true;
+ }
+ if (query.IsSeries ?? false)
+ {
+ programAttribtues.Add("IsSeries=@IsSeries");
+ cmd.Parameters.Add(cmd, "@IsSeries", DbType.Boolean).Value = true;
+ }
+ if (query.IsKids ?? false)
+ {
+ programAttribtues.Add("IsKids=@IsKids");
+ cmd.Parameters.Add(cmd, "@IsKids", DbType.Boolean).Value = true;
+ }
+ if (programAttribtues.Count > 0)
+ {
+ whereClauses.Add("(" + string.Join(" OR ", programAttribtues.ToArray()) + ")");
+ }
}
- if (query.IsSports.HasValue)
+
+ if (query.SimilarTo != null)
{
- whereClauses.Add("IsSports=@IsSports");
- cmd.Parameters.Add(cmd, "@IsSports", DbType.Boolean).Value = query.IsSports;
+ whereClauses.Add("SimilarityScore > 0");
}
+
if (query.IsFolder.HasValue)
{
whereClauses.Add("IsFolder=@IsFolder");
@@ -2626,6 +3205,18 @@ namespace MediaBrowser.Server.Implementations.Persistence
cmd.Parameters.Add(cmd, "@MinIndexNumber", DbType.Int32).Value = query.MinIndexNumber.Value;
}
+ if (query.MinDateCreated.HasValue)
+ {
+ whereClauses.Add("DateCreated>=@MinDateCreated");
+ cmd.Parameters.Add(cmd, "@MinDateCreated", DbType.DateTime).Value = query.MinDateCreated.Value;
+ }
+
+ if (query.MinDateLastSaved.HasValue)
+ {
+ whereClauses.Add("DateLastSaved>=@MinDateLastSaved");
+ cmd.Parameters.Add(cmd, "@MinDateLastSaved", DbType.DateTime).Value = query.MinDateLastSaved.Value;
+ }
+
//if (query.MinPlayers.HasValue)
//{
// whereClauses.Add("Players>=@MinPlayers");
@@ -2765,6 +3356,12 @@ namespace MediaBrowser.Server.Implementations.Persistence
cmd.Parameters.Add(cmd, "@MinSortName", DbType.String).Value = query.MinSortName;
}
+ if (!string.IsNullOrWhiteSpace(query.ExternalSeriesId))
+ {
+ whereClauses.Add("ExternalSeriesId=@ExternalSeriesId");
+ cmd.Parameters.Add(cmd, "@ExternalSeriesId", DbType.String).Value = query.ExternalSeriesId;
+ }
+
if (!string.IsNullOrWhiteSpace(query.Name))
{
whereClauses.Add("CleanName=@Name");
@@ -3235,6 +3832,28 @@ namespace MediaBrowser.Server.Implementations.Persistence
clause += ")";
whereClauses.Add(clause);
}
+ if (query.HasThemeSong.HasValue)
+ {
+ if (query.HasThemeSong.Value)
+ {
+ whereClauses.Add("ThemeSongIds not null");
+ }
+ else
+ {
+ whereClauses.Add("ThemeSongIds is null");
+ }
+ }
+ if (query.HasThemeVideo.HasValue)
+ {
+ if (query.HasThemeVideo.Value)
+ {
+ whereClauses.Add("ThemeVideoIds not null");
+ }
+ else
+ {
+ whereClauses.Add("ThemeVideoIds is null");
+ }
+ }
//var enableItemsByName = query.IncludeItemsByName ?? query.IncludeItemTypes.Length > 0;
var enableItemsByName = query.IncludeItemsByName ?? false;
diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteUserRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteUserRepository.cs
index 25ab60ca5..31fa78806 100644
--- a/MediaBrowser.Server.Implementations/Persistence/SqliteUserRepository.cs
+++ b/MediaBrowser.Server.Implementations/Persistence/SqliteUserRepository.cs
@@ -9,6 +9,7 @@ using System.Data;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Common.IO;
namespace MediaBrowser.Server.Implementations.Persistence
{
@@ -18,10 +19,12 @@ namespace MediaBrowser.Server.Implementations.Persistence
public class SqliteUserRepository : BaseSqliteRepository, IUserRepository
{
private readonly IJsonSerializer _jsonSerializer;
+ private readonly IMemoryStreamProvider _memoryStreamProvider;
- public SqliteUserRepository(ILogManager logManager, IServerApplicationPaths appPaths, IJsonSerializer jsonSerializer, IDbConnector dbConnector) : base(logManager, dbConnector)
+ public SqliteUserRepository(ILogManager logManager, IServerApplicationPaths appPaths, IJsonSerializer jsonSerializer, IDbConnector dbConnector, IMemoryStreamProvider memoryStreamProvider) : base(logManager, dbConnector)
{
_jsonSerializer = jsonSerializer;
+ _memoryStreamProvider = memoryStreamProvider;
DbFilePath = Path.Combine(appPaths.DataPath, "users.db");
}
@@ -75,7 +78,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
cancellationToken.ThrowIfCancellationRequested();
- var serialized = _jsonSerializer.SerializeToBytes(user);
+ var serialized = _jsonSerializer.SerializeToBytes(user, _memoryStreamProvider);
cancellationToken.ThrowIfCancellationRequested();
@@ -150,7 +153,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
{
var id = reader.GetGuid(0);
- using (var stream = reader.GetMemoryStream(1))
+ using (var stream = reader.GetMemoryStream(1, _memoryStreamProvider))
{
var user = _jsonSerializer.DeserializeFromStream<User>(stream);
user.Id = id;
diff --git a/MediaBrowser.Server.Implementations/Playlists/ManualPlaylistsFolder.cs b/MediaBrowser.Server.Implementations/Playlists/ManualPlaylistsFolder.cs
index ff0e4a0e0..63dfe20b2 100644
--- a/MediaBrowser.Server.Implementations/Playlists/ManualPlaylistsFolder.cs
+++ b/MediaBrowser.Server.Implementations/Playlists/ManualPlaylistsFolder.cs
@@ -5,6 +5,8 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using CommonIO;
+using MediaBrowser.Model.Querying;
+using System.Threading.Tasks;
namespace MediaBrowser.Server.Implementations.Playlists
{
@@ -17,7 +19,7 @@ namespace MediaBrowser.Server.Implementations.Playlists
public override bool IsVisible(User user)
{
- return base.IsVisible(user) && GetChildren(user, false).Any();
+ return base.IsVisible(user) && GetChildren(user, true).Any();
}
protected override IEnumerable<BaseItem> GetEligibleChildrenForRecursiveChildren(User user)
@@ -37,6 +39,12 @@ namespace MediaBrowser.Server.Implementations.Playlists
{
get { return Model.Entities.CollectionType.Playlists; }
}
+
+ protected override Task<QueryResult<BaseItem>> GetItemsInternal(InternalItemsQuery query)
+ {
+ query.Recursive = false;
+ return base.GetItemsInternal(query);
+ }
}
public class PlaylistsDynamicFolder : IVirtualFolderCreator
diff --git a/MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs b/MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs
index 33d106916..893592fa3 100644
--- a/MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs
+++ b/MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs
@@ -13,6 +13,7 @@ using System.Linq;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Common.IO;
namespace MediaBrowser.Server.Implementations.ServerManager
{
@@ -72,6 +73,7 @@ namespace MediaBrowser.Server.Implementations.ServerManager
private readonly List<IWebSocketListener> _webSocketListeners = new List<IWebSocketListener>();
private bool _disposed;
+ private readonly IMemoryStreamProvider _memoryStreamProvider;
/// <summary>
/// Initializes a new instance of the <see cref="ServerManager" /> class.
@@ -81,7 +83,7 @@ namespace MediaBrowser.Server.Implementations.ServerManager
/// <param name="logger">The logger.</param>
/// <param name="configurationManager">The configuration manager.</param>
/// <exception cref="System.ArgumentNullException">applicationHost</exception>
- public ServerManager(IServerApplicationHost applicationHost, IJsonSerializer jsonSerializer, ILogger logger, IServerConfigurationManager configurationManager)
+ public ServerManager(IServerApplicationHost applicationHost, IJsonSerializer jsonSerializer, ILogger logger, IServerConfigurationManager configurationManager, IMemoryStreamProvider memoryStreamProvider)
{
if (applicationHost == null)
{
@@ -100,6 +102,7 @@ namespace MediaBrowser.Server.Implementations.ServerManager
_jsonSerializer = jsonSerializer;
_applicationHost = applicationHost;
ConfigurationManager = configurationManager;
+ _memoryStreamProvider = memoryStreamProvider;
}
/// <summary>
@@ -150,7 +153,7 @@ namespace MediaBrowser.Server.Implementations.ServerManager
return;
}
- var connection = new WebSocketConnection(e.WebSocket, e.Endpoint, _jsonSerializer, _logger)
+ var connection = new WebSocketConnection(e.WebSocket, e.Endpoint, _jsonSerializer, _logger, _memoryStreamProvider)
{
OnReceive = ProcessWebSocketMessageReceived,
Url = e.Url,
diff --git a/MediaBrowser.Server.Implementations/ServerManager/WebSocketConnection.cs b/MediaBrowser.Server.Implementations/ServerManager/WebSocketConnection.cs
index 2adf3e86a..60b04cf82 100644
--- a/MediaBrowser.Server.Implementations/ServerManager/WebSocketConnection.cs
+++ b/MediaBrowser.Server.Implementations/ServerManager/WebSocketConnection.cs
@@ -9,6 +9,7 @@ using System.Collections.Specialized;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Common.IO;
using UniversalDetector;
namespace MediaBrowser.Server.Implementations.ServerManager
@@ -19,7 +20,7 @@ namespace MediaBrowser.Server.Implementations.ServerManager
public class WebSocketConnection : IWebSocketConnection
{
public event EventHandler<EventArgs> Closed;
-
+
/// <summary>
/// The _socket
/// </summary>
@@ -36,11 +37,6 @@ namespace MediaBrowser.Server.Implementations.ServerManager
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
/// <summary>
- /// The _send semaphore
- /// </summary>
- private readonly SemaphoreSlim _sendSemaphore = new SemaphoreSlim(1, 1);
-
- /// <summary>
/// The logger
/// </summary>
private readonly ILogger _logger;
@@ -78,7 +74,8 @@ namespace MediaBrowser.Server.Implementations.ServerManager
/// </summary>
/// <value>The query string.</value>
public NameValueCollection QueryString { get; set; }
-
+ private readonly IMemoryStreamProvider _memoryStreamProvider;
+
/// <summary>
/// Initializes a new instance of the <see cref="WebSocketConnection" /> class.
/// </summary>
@@ -87,7 +84,7 @@ namespace MediaBrowser.Server.Implementations.ServerManager
/// <param name="jsonSerializer">The json serializer.</param>
/// <param name="logger">The logger.</param>
/// <exception cref="System.ArgumentNullException">socket</exception>
- public WebSocketConnection(IWebSocket socket, string remoteEndPoint, IJsonSerializer jsonSerializer, ILogger logger)
+ public WebSocketConnection(IWebSocket socket, string remoteEndPoint, IJsonSerializer jsonSerializer, ILogger logger, IMemoryStreamProvider memoryStreamProvider)
{
if (socket == null)
{
@@ -113,6 +110,7 @@ namespace MediaBrowser.Server.Implementations.ServerManager
_socket.OnReceive = OnReceiveInternal;
RemoteEndPoint = remoteEndPoint;
_logger = logger;
+ _memoryStreamProvider = memoryStreamProvider;
socket.Closed += socket_Closed;
}
@@ -149,7 +147,7 @@ namespace MediaBrowser.Server.Implementations.ServerManager
{
try
{
- using (var ms = new MemoryStream(bytes))
+ using (var ms = _memoryStreamProvider.CreateNew(bytes))
{
var detector = new CharsetDetector();
detector.Feed(ms);
@@ -207,7 +205,7 @@ namespace MediaBrowser.Server.Implementations.ServerManager
_logger.ErrorException("Error processing web socket message", ex);
}
}
-
+
/// <summary>
/// Sends a message asynchronously.
/// </summary>
@@ -234,7 +232,7 @@ namespace MediaBrowser.Server.Implementations.ServerManager
/// <param name="buffer">The buffer.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
- public async Task SendAsync(byte[] buffer, CancellationToken cancellationToken)
+ public Task SendAsync(byte[] buffer, CancellationToken cancellationToken)
{
if (buffer == null)
{
@@ -243,33 +241,10 @@ namespace MediaBrowser.Server.Implementations.ServerManager
cancellationToken.ThrowIfCancellationRequested();
- // Per msdn docs, attempting to send simultaneous messages will result in one failing.
- // This should help us workaround that and ensure all messages get sent
- await _sendSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
-
- try
- {
- await _socket.SendAsync(buffer, true, cancellationToken);
- }
- catch (OperationCanceledException)
- {
- _logger.Info("WebSocket message to {0} was cancelled", RemoteEndPoint);
-
- throw;
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error sending WebSocket message {0}", ex, RemoteEndPoint);
-
- throw;
- }
- finally
- {
- _sendSemaphore.Release();
- }
+ return _socket.SendAsync(buffer, true, cancellationToken);
}
- public async Task SendAsync(string text, CancellationToken cancellationToken)
+ public Task SendAsync(string text, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(text))
{
@@ -278,30 +253,7 @@ namespace MediaBrowser.Server.Implementations.ServerManager
cancellationToken.ThrowIfCancellationRequested();
- // Per msdn docs, attempting to send simultaneous messages will result in one failing.
- // This should help us workaround that and ensure all messages get sent
- await _sendSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
-
- try
- {
- await _socket.SendAsync(text, true, cancellationToken);
- }
- catch (OperationCanceledException)
- {
- _logger.Info("WebSocket message to {0} was cancelled", RemoteEndPoint);
-
- throw;
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error sending WebSocket message {0}", ex, RemoteEndPoint);
-
- throw;
- }
- finally
- {
- _sendSemaphore.Release();
- }
+ return _socket.SendAsync(text, true, cancellationToken);
}
/// <summary>
diff --git a/MediaBrowser.Server.Implementations/Session/HttpSessionController.cs b/MediaBrowser.Server.Implementations/Session/HttpSessionController.cs
index b841f0216..f54c452cc 100644
--- a/MediaBrowser.Server.Implementations/Session/HttpSessionController.cs
+++ b/MediaBrowser.Server.Implementations/Session/HttpSessionController.cs
@@ -75,7 +75,8 @@ namespace MediaBrowser.Server.Implementations.Session
await _httpClient.Post(new HttpRequestOptions
{
Url = url,
- CancellationToken = cancellationToken
+ CancellationToken = cancellationToken,
+ BufferContent = false
}).ConfigureAwait(false);
}
diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs
index afcdf9d90..e898a6abd 100644
--- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs
+++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs
@@ -41,12 +41,7 @@ namespace MediaBrowser.Server.Implementations.Session
/// <summary>
/// The _user data repository
/// </summary>
- private readonly IUserDataManager _userDataRepository;
-
- /// <summary>
- /// The _user repository
- /// </summary>
- private readonly IUserRepository _userRepository;
+ private readonly IUserDataManager _userDataManager;
/// <summary>
/// The _logger
@@ -99,11 +94,10 @@ namespace MediaBrowser.Server.Implementations.Session
private readonly SemaphoreSlim _sessionLock = new SemaphoreSlim(1, 1);
- public SessionManager(IUserDataManager userDataRepository, ILogger logger, IUserRepository userRepository, ILibraryManager libraryManager, IUserManager userManager, IMusicManager musicManager, IDtoService dtoService, IImageProcessor imageProcessor, IJsonSerializer jsonSerializer, IServerApplicationHost appHost, IHttpClient httpClient, IAuthenticationRepository authRepo, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager)
+ public SessionManager(IUserDataManager userDataManager, ILogger logger, ILibraryManager libraryManager, IUserManager userManager, IMusicManager musicManager, IDtoService dtoService, IImageProcessor imageProcessor, IJsonSerializer jsonSerializer, IServerApplicationHost appHost, IHttpClient httpClient, IAuthenticationRepository authRepo, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager)
{
- _userDataRepository = userDataRepository;
+ _userDataManager = userDataManager;
_logger = logger;
- _userRepository = userRepository;
_libraryManager = libraryManager;
_userManager = userManager;
_musicManager = musicManager;
@@ -248,13 +242,11 @@ namespace MediaBrowser.Server.Implementations.Session
var userLastActivityDate = user.LastActivityDate ?? DateTime.MinValue;
user.LastActivityDate = activityDate;
- // Don't log in the db anymore frequently than 10 seconds
- if ((activityDate - userLastActivityDate).TotalSeconds > 10)
+ if ((activityDate - userLastActivityDate).TotalSeconds > 60)
{
try
{
- // Save this directly. No need to fire off all the events for this.
- await _userRepository.SaveUser(user, CancellationToken.None).ConfigureAwait(false);
+ await _userManager.UpdateUser(user).ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -294,11 +286,9 @@ namespace MediaBrowser.Server.Implementations.Session
var key = GetSessionKey(session.Client, session.DeviceId);
SessionInfo removed;
+ _activeConnections.TryRemove(key, out removed);
- if (_activeConnections.TryRemove(key, out removed))
- {
- OnSessionEnded(removed);
- }
+ OnSessionEnded(session);
}
}
finally
@@ -307,9 +297,9 @@ namespace MediaBrowser.Server.Implementations.Session
}
}
- private Task<MediaSourceInfo> GetMediaSource(IHasMediaSources item, string mediaSourceId)
+ private Task<MediaSourceInfo> GetMediaSource(IHasMediaSources item, string mediaSourceId, string liveStreamId)
{
- return _mediaSourceManager.GetMediaSource(item, mediaSourceId, false);
+ return _mediaSourceManager.GetMediaSource(item, mediaSourceId, liveStreamId, false, CancellationToken.None);
}
/// <summary>
@@ -337,7 +327,7 @@ namespace MediaBrowser.Server.Implementations.Session
var hasMediaSources = libraryItem as IHasMediaSources;
if (hasMediaSources != null)
{
- mediaSource = await GetMediaSource(hasMediaSources, info.MediaSourceId).ConfigureAwait(false);
+ mediaSource = await GetMediaSource(hasMediaSources, info.MediaSourceId, info.LiveStreamId).ConfigureAwait(false);
if (mediaSource != null)
{
@@ -638,17 +628,21 @@ namespace MediaBrowser.Server.Implementations.Session
/// <returns>Task.</returns>
private async Task OnPlaybackStart(Guid userId, IHasUserData item)
{
- var data = _userDataRepository.GetUserData(userId, item);
+ var data = _userDataManager.GetUserData(userId, item);
data.PlayCount++;
data.LastPlayedDate = DateTime.UtcNow;
- if (!(item is Video))
+ if (!(item is Video) && item.SupportsPlayedStatus)
{
data.Played = true;
}
+ else
+ {
+ data.Played = false;
+ }
- await _userDataRepository.SaveUserData(userId, item, data, UserDataSaveReason.PlaybackStart, CancellationToken.None).ConfigureAwait(false);
+ await _userDataManager.SaveUserData(userId, item, data, UserDataSaveReason.PlaybackStart, CancellationToken.None).ConfigureAwait(false);
}
/// <summary>
@@ -715,17 +709,17 @@ namespace MediaBrowser.Server.Implementations.Session
private async Task OnPlaybackProgress(User user, BaseItem item, PlaybackProgressInfo info)
{
- var data = _userDataRepository.GetUserData(user.Id, item);
+ var data = _userDataManager.GetUserData(user.Id, item);
var positionTicks = info.PositionTicks;
if (positionTicks.HasValue)
{
- _userDataRepository.UpdatePlayState(item, data, positionTicks.Value);
+ _userDataManager.UpdatePlayState(item, data, positionTicks.Value);
UpdatePlaybackSettings(user, info, data);
- await _userDataRepository.SaveUserData(user.Id, item, data, UserDataSaveReason.PlaybackProgress, CancellationToken.None).ConfigureAwait(false);
+ await _userDataManager.SaveUserData(user.Id, item, data, UserDataSaveReason.PlaybackProgress, CancellationToken.None).ConfigureAwait(false);
}
}
@@ -792,7 +786,7 @@ namespace MediaBrowser.Server.Implementations.Session
var hasMediaSources = libraryItem as IHasMediaSources;
if (hasMediaSources != null)
{
- mediaSource = await GetMediaSource(hasMediaSources, info.MediaSourceId).ConfigureAwait(false);
+ mediaSource = await GetMediaSource(hasMediaSources, info.MediaSourceId, info.LiveStreamId).ConfigureAwait(false);
}
info.Item = GetItemInfo(libraryItem, libraryItem, mediaSource);
@@ -820,7 +814,7 @@ namespace MediaBrowser.Server.Implementations.Session
{
try
{
- await _mediaSourceManager.CloseLiveStream(info.LiveStreamId, CancellationToken.None).ConfigureAwait(false);
+ await _mediaSourceManager.CloseLiveStream(info.LiveStreamId).ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -851,22 +845,22 @@ namespace MediaBrowser.Server.Implementations.Session
if (!playbackFailed)
{
- var data = _userDataRepository.GetUserData(userId, item);
+ var data = _userDataManager.GetUserData(userId, item);
if (positionTicks.HasValue)
{
- playedToCompletion = _userDataRepository.UpdatePlayState(item, data, positionTicks.Value);
+ playedToCompletion = _userDataManager.UpdatePlayState(item, data, positionTicks.Value);
}
- else
+ else
{
// If the client isn't able to report this, then we'll just have to make an assumption
data.PlayCount++;
- data.Played = true;
+ data.Played = item.SupportsPlayedStatus;
data.PlaybackPositionTicks = 0;
playedToCompletion = true;
}
- await _userDataRepository.SaveUserData(userId, item, data, UserDataSaveReason.PlaybackFinished, CancellationToken.None).ConfigureAwait(false);
+ await _userDataManager.SaveUserData(userId, item, data, UserDataSaveReason.PlaybackFinished, CancellationToken.None).ConfigureAwait(false);
}
return playedToCompletion;
diff --git a/MediaBrowser.Server.Implementations/Sync/SyncManager.cs b/MediaBrowser.Server.Implementations/Sync/SyncManager.cs
index 1278a40a4..c523ec7bd 100644
--- a/MediaBrowser.Server.Implementations/Sync/SyncManager.cs
+++ b/MediaBrowser.Server.Implementations/Sync/SyncManager.cs
@@ -30,6 +30,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
+using MediaBrowser.Common.IO;
namespace MediaBrowser.Server.Implementations.Sync
{
@@ -51,6 +52,7 @@ namespace MediaBrowser.Server.Implementations.Sync
private readonly Func<IMediaSourceManager> _mediaSourceManager;
private readonly IJsonSerializer _json;
private readonly ITaskManager _taskManager;
+ private readonly IMemoryStreamProvider _memoryStreamProvider;
private ISyncProvider[] _providers = { };
@@ -60,7 +62,7 @@ namespace MediaBrowser.Server.Implementations.Sync
public event EventHandler<GenericEventArgs<SyncJobItem>> SyncJobItemUpdated;
public event EventHandler<GenericEventArgs<SyncJobItem>> SyncJobItemCreated;
- public SyncManager(ILibraryManager libraryManager, ISyncRepository repo, IImageProcessor imageProcessor, ILogger logger, IUserManager userManager, Func<IDtoService> dtoService, IServerApplicationHost appHost, ITVSeriesManager tvSeriesManager, Func<IMediaEncoder> mediaEncoder, IFileSystem fileSystem, Func<ISubtitleEncoder> subtitleEncoder, IConfigurationManager config, IUserDataManager userDataManager, Func<IMediaSourceManager> mediaSourceManager, IJsonSerializer json, ITaskManager taskManager)
+ public SyncManager(ILibraryManager libraryManager, ISyncRepository repo, IImageProcessor imageProcessor, ILogger logger, IUserManager userManager, Func<IDtoService> dtoService, IServerApplicationHost appHost, ITVSeriesManager tvSeriesManager, Func<IMediaEncoder> mediaEncoder, IFileSystem fileSystem, Func<ISubtitleEncoder> subtitleEncoder, IConfigurationManager config, IUserDataManager userDataManager, Func<IMediaSourceManager> mediaSourceManager, IJsonSerializer json, ITaskManager taskManager, IMemoryStreamProvider memoryStreamProvider)
{
_libraryManager = libraryManager;
_repo = repo;
@@ -78,6 +80,7 @@ namespace MediaBrowser.Server.Implementations.Sync
_mediaSourceManager = mediaSourceManager;
_json = json;
_taskManager = taskManager;
+ _memoryStreamProvider = memoryStreamProvider;
}
public void AddParts(IEnumerable<ISyncProvider> providers)
@@ -95,7 +98,7 @@ namespace MediaBrowser.Server.Implementations.Sync
public ISyncDataProvider GetDataProvider(IServerSyncProvider provider, SyncTarget target)
{
- return _dataProviders.GetOrAdd(target.Id, key => new TargetDataProvider(provider, target, _appHost, _logger, _json, _fileSystem, _config.CommonApplicationPaths));
+ return _dataProviders.GetOrAdd(target.Id, key => new TargetDataProvider(provider, target, _appHost, _logger, _json, _fileSystem, _config.CommonApplicationPaths, _memoryStreamProvider));
}
public async Task<SyncJobCreationResult> CreateJob(SyncJobRequest request)
diff --git a/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs b/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs
index a2b5851ac..e0553b1b1 100644
--- a/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs
+++ b/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs
@@ -99,7 +99,7 @@ namespace MediaBrowser.Server.Implementations.Sync
// Do not use a pipe here because Roku http requests to the server will fail, without any explicit error message.
private const string StreamIdDelimeterString = "_";
- public async Task<MediaSourceInfo> OpenMediaSource(string openToken, CancellationToken cancellationToken)
+ public async Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> OpenMediaSource(string openToken, CancellationToken cancellationToken)
{
var openKeys = openToken.Split(new[] { StreamIdDelimeterString[0] }, 3);
@@ -137,7 +137,7 @@ namespace MediaBrowser.Server.Implementations.Sync
mediaSource.Protocol = dynamicInfo.Protocol;
mediaSource.RequiredHttpHeaders = dynamicInfo.RequiredHttpHeaders;
- return mediaSource;
+ return new Tuple<MediaSourceInfo, IDirectStreamProvider>(mediaSource, null);
}
private void SetStaticMediaSourceInfo(LocalItem item, MediaSourceInfo mediaSource)
@@ -150,7 +150,7 @@ namespace MediaBrowser.Server.Implementations.Sync
}
}
- public Task CloseMediaSource(string liveStreamId, CancellationToken cancellationToken)
+ public Task CloseMediaSource(string liveStreamId)
{
throw new NotImplementedException();
}
diff --git a/MediaBrowser.Server.Implementations/Sync/TargetDataProvider.cs b/MediaBrowser.Server.Implementations/Sync/TargetDataProvider.cs
index 106dc9115..32a600371 100644
--- a/MediaBrowser.Server.Implementations/Sync/TargetDataProvider.cs
+++ b/MediaBrowser.Server.Implementations/Sync/TargetDataProvider.cs
@@ -12,6 +12,7 @@ using System.Threading;
using System.Threading.Tasks;
using CommonIO;
using Interfaces.IO;
+using MediaBrowser.Common.IO;
namespace MediaBrowser.Server.Implementations.Sync
{
@@ -28,8 +29,9 @@ namespace MediaBrowser.Server.Implementations.Sync
private readonly IFileSystem _fileSystem;
private readonly IApplicationPaths _appPaths;
private readonly IServerApplicationHost _appHost;
+ private readonly IMemoryStreamProvider _memoryStreamProvider;
- public TargetDataProvider(IServerSyncProvider provider, SyncTarget target, IServerApplicationHost appHost, ILogger logger, IJsonSerializer json, IFileSystem fileSystem, IApplicationPaths appPaths)
+ public TargetDataProvider(IServerSyncProvider provider, SyncTarget target, IServerApplicationHost appHost, ILogger logger, IJsonSerializer json, IFileSystem fileSystem, IApplicationPaths appPaths, IMemoryStreamProvider memoryStreamProvider)
{
_logger = logger;
_json = json;
@@ -37,6 +39,7 @@ namespace MediaBrowser.Server.Implementations.Sync
_target = target;
_fileSystem = fileSystem;
_appPaths = appPaths;
+ _memoryStreamProvider = memoryStreamProvider;
_appHost = appHost;
}
@@ -90,7 +93,7 @@ namespace MediaBrowser.Server.Implementations.Sync
private async Task SaveData(List<LocalItem> items, CancellationToken cancellationToken)
{
- using (var stream = new MemoryStream())
+ using (var stream = _memoryStreamProvider.CreateNew())
{
_json.SerializeToStream(items, stream);
diff --git a/MediaBrowser.Server.Implementations/app.config b/MediaBrowser.Server.Implementations/app.config
index 14f2f055f..77b8b9218 100644
--- a/MediaBrowser.Server.Implementations/app.config
+++ b/MediaBrowser.Server.Implementations/app.config
@@ -1,11 +1,11 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
- <assemblyIdentity name="System.Data.SQLite" publicKeyToken="db937bc2d44ff139" culture="neutral" />
- <bindingRedirect oldVersion="0.0.0.0-1.0.94.0" newVersion="1.0.94.0" />
+ <assemblyIdentity name="System.Data.SQLite" publicKeyToken="db937bc2d44ff139" culture="neutral"/>
+ <bindingRedirect oldVersion="0.0.0.0-1.0.94.0" newVersion="1.0.94.0"/>
</dependentAssembly>
</assemblyBinding>
</runtime>
-</configuration> \ No newline at end of file
+<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.1"/></startup></configuration>
diff --git a/MediaBrowser.Server.Implementations/packages.config b/MediaBrowser.Server.Implementations/packages.config
index 746dc7f62..043257fc8 100644
--- a/MediaBrowser.Server.Implementations/packages.config
+++ b/MediaBrowser.Server.Implementations/packages.config
@@ -5,9 +5,8 @@
<package id="ini-parser" version="2.3.0" targetFramework="net45" />
<package id="Interfaces.IO" version="1.0.0.5" targetFramework="net45" />
<package id="MediaBrowser.Naming" version="1.0.0.55" targetFramework="net45" />
- <package id="Mono.Nat" version="1.2.24.0" targetFramework="net45" />
<package id="morelinq" version="1.4.0" targetFramework="net45" />
<package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" />
- <package id="SimpleInjector" version="3.2.0" targetFramework="net45" />
- <package id="SocketHttpListener" version="1.0.0.39" targetFramework="net45" />
+ <package id="SimpleInjector" version="3.2.2" targetFramework="net45" />
+ <package id="SocketHttpListener" version="1.0.0.40" targetFramework="net45" />
</packages> \ No newline at end of file
diff --git a/MediaBrowser.Server.Mac.sln b/MediaBrowser.Server.Mac.sln
index 8a60fc6c1..d1e2e4621 100644
--- a/MediaBrowser.Server.Mac.sln
+++ b/MediaBrowser.Server.Mac.sln
@@ -33,6 +33,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenSubtitlesHandler", "Ope
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Emby.Drawing", "Emby.Drawing\Emby.Drawing.csproj", "{08FFF49B-F175-4807-A2B5-73B0EBD9F716}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mono.Nat", "Mono.Nat\Mono.Nat.csproj", "{D7453B88-2266-4805-B39B-2B5A2A33E1BA}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -268,6 +270,20 @@ Global
{C97B98FA-00D4-4880-88B8-C76017A418AB}.Release|Any CPU.Build.0 = Release|Any CPU
{C97B98FA-00D4-4880-88B8-C76017A418AB}.Release|x86.ActiveCfg = Release|Any CPU
{C97B98FA-00D4-4880-88B8-C76017A418AB}.Release|x86.Build.0 = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.AppStore|Any CPU.Build.0 = Debug|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|Any CPU.ActiveCfg = Debug|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|Any CPU.Build.0 = Debug|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|x86.Build.0 = Debug|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|x86.ActiveCfg = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|x86.Build.0 = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|x86.ActiveCfg = Debug|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|x86.Build.0 = Debug|Any CPU
EndGlobalSection
GlobalSection(MonoDevelopProperties) = preSolution
Policies = $0
diff --git a/MediaBrowser.Server.Mac/Emby.Server.Mac.csproj b/MediaBrowser.Server.Mac/Emby.Server.Mac.csproj
index 9f3bf5102..36fda1e70 100644
--- a/MediaBrowser.Server.Mac/Emby.Server.Mac.csproj
+++ b/MediaBrowser.Server.Mac/Emby.Server.Mac.csproj
@@ -1005,6 +1005,30 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\cryptojslib\components\x64-core.js">
<Link>Resources\dashboard-ui\bower_components\cryptojslib\components\x64-core.js</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\document-register-element\.bower.json">
+ <Link>Resources\dashboard-ui\bower_components\document-register-element\.bower.json</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\document-register-element\LICENSE.txt">
+ <Link>Resources\dashboard-ui\bower_components\document-register-element\LICENSE.txt</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\document-register-element\README.md">
+ <Link>Resources\dashboard-ui\bower_components\document-register-element\README.md</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\document-register-element\RESOURCES.md">
+ <Link>Resources\dashboard-ui\bower_components\document-register-element\RESOURCES.md</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\document-register-element\basic.html">
+ <Link>Resources\dashboard-ui\bower_components\document-register-element\basic.html</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\document-register-element\bower.json">
+ <Link>Resources\dashboard-ui\bower_components\document-register-element\bower.json</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\document-register-element\testrunner.js">
+ <Link>Resources\dashboard-ui\bower_components\document-register-element\testrunner.js</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\document-register-element\build\document-register-element.js">
+ <Link>Resources\dashboard-ui\bower_components\document-register-element\build\document-register-element.js</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-apiclient\.bower.json">
<Link>Resources\dashboard-ui\bower_components\emby-apiclient\.bower.json</Link>
</BundleResource>
@@ -1284,6 +1308,12 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\emby-textarea\emby-textarea.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\emby-textarea\emby-textarea.js</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\emby-toggle\emby-toggle.css">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\emby-toggle\emby-toggle.css</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\emby-toggle\emby-toggle.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\emby-toggle\emby-toggle.js</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\fonts\material-icons\2fcrYFNaTjcS6g4U3t-Y5ZjZjT5FdEJ140U2DJYC3mY.woff2">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\fonts\material-icons\2fcrYFNaTjcS6g4U3t-Y5ZjZjT5FdEJ140U2DJYC3mY.woff2</Link>
</BundleResource>
@@ -1479,6 +1509,12 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\fullscreen\fullscreenmanager.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\fullscreen\fullscreenmanager.js</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\guide\guide-categories.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\guide\guide-categories.js</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\guide\guide-categories.template.html">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\guide\guide-categories.template.html</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\guide\guide-settings.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\guide\guide-settings.js</Link>
</BundleResource>
@@ -1491,6 +1527,9 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\guide\guide.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\guide\guide.js</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\guide\programs.css">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\guide\programs.css</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\guide\tvguide.template.html">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\guide\tvguide.template.html</Link>
</BundleResource>
@@ -1506,6 +1545,9 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\images\imagehelper.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\images\imagehelper.js</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\images\indexeddbimagefetcher.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\images\indexeddbimagefetcher.js</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\images\persistentimagefetcher.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\images\persistentimagefetcher.js</Link>
</BundleResource>
@@ -1614,6 +1656,9 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\prompt\prompt.template.html">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\prompt\prompt.template.html</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\recordingcreator\empty.png">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\recordingcreator\empty.png</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\recordingcreator\recordingcreator.css">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\recordingcreator\recordingcreator.css</Link>
</BundleResource>
@@ -1629,6 +1674,21 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\recordingcreator\recordingeditor.template.html">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\recordingcreator\recordingeditor.template.html</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\recordingcreator\recordingfields.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\recordingcreator\recordingfields.js</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\recordingcreator\recordingfields.template.html">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\recordingcreator\recordingfields.template.html</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\recordingcreator\recordinghelper.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\recordingcreator\recordinghelper.js</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\recordingcreator\seriesrecordingeditor.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\recordingcreator\seriesrecordingeditor.js</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\recordingcreator\seriesrecordingeditor.template.html">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\recordingcreator\seriesrecordingeditor.template.html</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\refreshdialog\refreshdialog.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\refreshdialog\refreshdialog.js</Link>
</BundleResource>
@@ -1656,32 +1716,32 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\sharing\sharingmenu.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\sharing\sharingmenu.js</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.4\LICENSE">
- <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.4\LICENSE</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.10\LICENSE">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.10\LICENSE</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.4\README.md">
- <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.4\README.md</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.10\README.md">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.10\README.md</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.4\dist\css\social-share-kit.css">
- <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.4\dist\css\social-share-kit.css</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.10\dist\css\social-share-kit.css">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.10\dist\css\social-share-kit.css</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.4\dist\fonts\social-share-kit.eot">
- <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.4\dist\fonts\social-share-kit.eot</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.10\dist\fonts\social-share-kit.eot">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.10\dist\fonts\social-share-kit.eot</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.4\dist\fonts\social-share-kit.svg">
- <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.4\dist\fonts\social-share-kit.svg</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.10\dist\fonts\social-share-kit.svg">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.10\dist\fonts\social-share-kit.svg</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.4\dist\fonts\social-share-kit.ttf">
- <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.4\dist\fonts\social-share-kit.ttf</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.10\dist\fonts\social-share-kit.ttf">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.10\dist\fonts\social-share-kit.ttf</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.4\dist\fonts\social-share-kit.woff">
- <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.4\dist\fonts\social-share-kit.woff</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.10\dist\fonts\social-share-kit.woff">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.10\dist\fonts\social-share-kit.woff</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.4\dist\js\social-share-kit.js">
- <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.4\dist\js\social-share-kit.js</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.10\dist\js\social-share-kit.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.10\dist\js\social-share-kit.js</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.4\dist\js\social-share-kit.min.js">
- <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.4\dist\js\social-share-kit.min.js</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.10\dist\js\social-share-kit.min.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.10\dist\js\social-share-kit.min.js</Link>
</BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\slideshow\slideshow.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\slideshow\slideshow.js</Link>
@@ -1953,626 +2013,86 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\hammerjs\hammer.min.map">
<Link>Resources\dashboard-ui\bower_components\hammerjs\hammer.min.map</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\headroom.js\.bower.json">
- <Link>Resources\dashboard-ui\bower_components\headroom.js\.bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\headroom.js\LICENSE">
- <Link>Resources\dashboard-ui\bower_components\headroom.js\LICENSE</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\headroom.js\README.md">
- <Link>Resources\dashboard-ui\bower_components\headroom.js\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\headroom.js\bower.json">
- <Link>Resources\dashboard-ui\bower_components\headroom.js\bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\headroom.js\package.json">
- <Link>Resources\dashboard-ui\bower_components\headroom.js\package.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\headroom.js\dist\angular.headroom.js">
- <Link>Resources\dashboard-ui\bower_components\headroom.js\dist\angular.headroom.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\headroom.js\dist\angular.headroom.min.js">
- <Link>Resources\dashboard-ui\bower_components\headroom.js\dist\angular.headroom.min.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\headroom.js\dist\headroom.js">
- <Link>Resources\dashboard-ui\bower_components\headroom.js\dist\headroom.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\headroom.js\dist\headroom.min.js">
- <Link>Resources\dashboard-ui\bower_components\headroom.js\dist\headroom.min.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\headroom.js\dist\jQuery.headroom.js">
- <Link>Resources\dashboard-ui\bower_components\headroom.js\dist\jQuery.headroom.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\headroom.js\dist\jQuery.headroom.min.js">
- <Link>Resources\dashboard-ui\bower_components\headroom.js\dist\jQuery.headroom.min.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\hls.js\.bower.json">
- <Link>Resources\dashboard-ui\bower_components\hls.js\.bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\hls.js\API.md">
- <Link>Resources\dashboard-ui\bower_components\hls.js\API.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\hls.js\LICENSE">
- <Link>Resources\dashboard-ui\bower_components\hls.js\LICENSE</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\hls.js\README.md">
- <Link>Resources\dashboard-ui\bower_components\hls.js\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\hls.js\bower.json">
- <Link>Resources\dashboard-ui\bower_components\hls.js\bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\hls.js\design.md">
- <Link>Resources\dashboard-ui\bower_components\hls.js\design.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\hls.js\hls.js.sublime-project">
- <Link>Resources\dashboard-ui\bower_components\hls.js\hls.js.sublime-project</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\hls.js\package.json">
- <Link>Resources\dashboard-ui\bower_components\hls.js\package.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\hls.js\dist\hls.min.js">
- <Link>Resources\dashboard-ui\bower_components\hls.js\dist\hls.min.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\howler.js\.bower.json">
- <Link>Resources\dashboard-ui\bower_components\howler.js\.bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\howler.js\CHANGELOG.md">
- <Link>Resources\dashboard-ui\bower_components\howler.js\CHANGELOG.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\howler.js\LICENSE.md">
- <Link>Resources\dashboard-ui\bower_components\howler.js\LICENSE.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\howler.js\README.md">
- <Link>Resources\dashboard-ui\bower_components\howler.js\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\howler.js\bower.json">
- <Link>Resources\dashboard-ui\bower_components\howler.js\bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\howler.js\howler.min.js">
- <Link>Resources\dashboard-ui\bower_components\howler.js\howler.min.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\howler.js\package.json">
- <Link>Resources\dashboard-ui\bower_components\howler.js\package.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-a11y-announcer\.bower.json">
- <Link>Resources\dashboard-ui\bower_components\iron-a11y-announcer\.bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-a11y-announcer\.gitignore">
- <Link>Resources\dashboard-ui\bower_components\iron-a11y-announcer\.gitignore</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-a11y-announcer\.travis.yml">
- <Link>Resources\dashboard-ui\bower_components\iron-a11y-announcer\.travis.yml</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-a11y-announcer\CONTRIBUTING.md">
- <Link>Resources\dashboard-ui\bower_components\iron-a11y-announcer\CONTRIBUTING.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-a11y-announcer\README.md">
- <Link>Resources\dashboard-ui\bower_components\iron-a11y-announcer\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-a11y-announcer\bower.json">
- <Link>Resources\dashboard-ui\bower_components\iron-a11y-announcer\bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-a11y-announcer\index.html">
- <Link>Resources\dashboard-ui\bower_components\iron-a11y-announcer\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-a11y-announcer\iron-a11y-announcer.html">
- <Link>Resources\dashboard-ui\bower_components\iron-a11y-announcer\iron-a11y-announcer.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-a11y-announcer\.github\ISSUE_TEMPLATE.md">
- <Link>Resources\dashboard-ui\bower_components\iron-a11y-announcer\.github\ISSUE_TEMPLATE.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-a11y-announcer\demo\index.html">
- <Link>Resources\dashboard-ui\bower_components\iron-a11y-announcer\demo\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-a11y-announcer\demo\x-announces.html">
- <Link>Resources\dashboard-ui\bower_components\iron-a11y-announcer\demo\x-announces.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-a11y-announcer\test\index.html">
- <Link>Resources\dashboard-ui\bower_components\iron-a11y-announcer\test\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-a11y-announcer\test\iron-a11y-announcer.html">
- <Link>Resources\dashboard-ui\bower_components\iron-a11y-announcer\test\iron-a11y-announcer.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-a11y-keys-behavior\.bower.json">
- <Link>Resources\dashboard-ui\bower_components\iron-a11y-keys-behavior\.bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-a11y-keys-behavior\.gitignore">
- <Link>Resources\dashboard-ui\bower_components\iron-a11y-keys-behavior\.gitignore</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-a11y-keys-behavior\.travis.yml">
- <Link>Resources\dashboard-ui\bower_components\iron-a11y-keys-behavior\.travis.yml</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-a11y-keys-behavior\CONTRIBUTING.md">
- <Link>Resources\dashboard-ui\bower_components\iron-a11y-keys-behavior\CONTRIBUTING.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-a11y-keys-behavior\README.md">
- <Link>Resources\dashboard-ui\bower_components\iron-a11y-keys-behavior\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-a11y-keys-behavior\bower.json">
- <Link>Resources\dashboard-ui\bower_components\iron-a11y-keys-behavior\bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-a11y-keys-behavior\index.html">
- <Link>Resources\dashboard-ui\bower_components\iron-a11y-keys-behavior\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-a11y-keys-behavior\iron-a11y-keys-behavior.html">
- <Link>Resources\dashboard-ui\bower_components\iron-a11y-keys-behavior\iron-a11y-keys-behavior.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-a11y-keys-behavior\.github\ISSUE_TEMPLATE.md">
- <Link>Resources\dashboard-ui\bower_components\iron-a11y-keys-behavior\.github\ISSUE_TEMPLATE.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-a11y-keys-behavior\demo\index.html">
- <Link>Resources\dashboard-ui\bower_components\iron-a11y-keys-behavior\demo\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-a11y-keys-behavior\demo\x-key-aware.html">
- <Link>Resources\dashboard-ui\bower_components\iron-a11y-keys-behavior\demo\x-key-aware.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-a11y-keys-behavior\test\basic-test.html">
- <Link>Resources\dashboard-ui\bower_components\iron-a11y-keys-behavior\test\basic-test.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-a11y-keys-behavior\test\index.html">
- <Link>Resources\dashboard-ui\bower_components\iron-a11y-keys-behavior\test\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-behaviors\.bower.json">
- <Link>Resources\dashboard-ui\bower_components\iron-behaviors\.bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-behaviors\.gitignore">
- <Link>Resources\dashboard-ui\bower_components\iron-behaviors\.gitignore</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-behaviors\.travis.yml">
- <Link>Resources\dashboard-ui\bower_components\iron-behaviors\.travis.yml</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-behaviors\CONTRIBUTING.md">
- <Link>Resources\dashboard-ui\bower_components\iron-behaviors\CONTRIBUTING.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-behaviors\README.md">
- <Link>Resources\dashboard-ui\bower_components\iron-behaviors\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-behaviors\bower.json">
- <Link>Resources\dashboard-ui\bower_components\iron-behaviors\bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-behaviors\index.html">
- <Link>Resources\dashboard-ui\bower_components\iron-behaviors\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-behaviors\iron-button-state.html">
- <Link>Resources\dashboard-ui\bower_components\iron-behaviors\iron-button-state.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-behaviors\iron-control-state.html">
- <Link>Resources\dashboard-ui\bower_components\iron-behaviors\iron-control-state.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-behaviors\.github\ISSUE_TEMPLATE.md">
- <Link>Resources\dashboard-ui\bower_components\iron-behaviors\.github\ISSUE_TEMPLATE.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-behaviors\demo\index.html">
- <Link>Resources\dashboard-ui\bower_components\iron-behaviors\demo\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-behaviors\demo\simple-button.html">
- <Link>Resources\dashboard-ui\bower_components\iron-behaviors\demo\simple-button.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-behaviors\test\active-state.html">
- <Link>Resources\dashboard-ui\bower_components\iron-behaviors\test\active-state.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-behaviors\test\disabled-state.html">
- <Link>Resources\dashboard-ui\bower_components\iron-behaviors\test\disabled-state.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-behaviors\test\focused-state.html">
- <Link>Resources\dashboard-ui\bower_components\iron-behaviors\test\focused-state.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-behaviors\test\index.html">
- <Link>Resources\dashboard-ui\bower_components\iron-behaviors\test\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-behaviors\test\test-elements.html">
- <Link>Resources\dashboard-ui\bower_components\iron-behaviors\test\test-elements.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-checked-element-behavior\.bower.json">
- <Link>Resources\dashboard-ui\bower_components\iron-checked-element-behavior\.bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-checked-element-behavior\.gitignore">
- <Link>Resources\dashboard-ui\bower_components\iron-checked-element-behavior\.gitignore</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-checked-element-behavior\.travis.yml">
- <Link>Resources\dashboard-ui\bower_components\iron-checked-element-behavior\.travis.yml</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-checked-element-behavior\CONTRIBUTING.md">
- <Link>Resources\dashboard-ui\bower_components\iron-checked-element-behavior\CONTRIBUTING.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-checked-element-behavior\README.md">
- <Link>Resources\dashboard-ui\bower_components\iron-checked-element-behavior\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-checked-element-behavior\bower.json">
- <Link>Resources\dashboard-ui\bower_components\iron-checked-element-behavior\bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-checked-element-behavior\index.html">
- <Link>Resources\dashboard-ui\bower_components\iron-checked-element-behavior\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-checked-element-behavior\iron-checked-element-behavior.html">
- <Link>Resources\dashboard-ui\bower_components\iron-checked-element-behavior\iron-checked-element-behavior.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-checked-element-behavior\.github\ISSUE_TEMPLATE.md">
- <Link>Resources\dashboard-ui\bower_components\iron-checked-element-behavior\.github\ISSUE_TEMPLATE.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-checked-element-behavior\demo\index.html">
- <Link>Resources\dashboard-ui\bower_components\iron-checked-element-behavior\demo\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-checked-element-behavior\demo\simple-checkbox.html">
- <Link>Resources\dashboard-ui\bower_components\iron-checked-element-behavior\demo\simple-checkbox.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-checked-element-behavior\test\basic.html">
- <Link>Resources\dashboard-ui\bower_components\iron-checked-element-behavior\test\basic.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-checked-element-behavior\test\index.html">
- <Link>Resources\dashboard-ui\bower_components\iron-checked-element-behavior\test\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-checked-element-behavior\test\simple-checkbox.html">
- <Link>Resources\dashboard-ui\bower_components\iron-checked-element-behavior\test\simple-checkbox.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-demo-helpers\.bower.json">
- <Link>Resources\dashboard-ui\bower_components\iron-demo-helpers\.bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-demo-helpers\.gitignore">
- <Link>Resources\dashboard-ui\bower_components\iron-demo-helpers\.gitignore</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-demo-helpers\.travis.yml">
- <Link>Resources\dashboard-ui\bower_components\iron-demo-helpers\.travis.yml</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-demo-helpers\CONTRIBUTING.md">
- <Link>Resources\dashboard-ui\bower_components\iron-demo-helpers\CONTRIBUTING.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-demo-helpers\README.md">
- <Link>Resources\dashboard-ui\bower_components\iron-demo-helpers\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-demo-helpers\bower.json">
- <Link>Resources\dashboard-ui\bower_components\iron-demo-helpers\bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-demo-helpers\demo-pages-shared-styles.html">
- <Link>Resources\dashboard-ui\bower_components\iron-demo-helpers\demo-pages-shared-styles.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-demo-helpers\demo-snippet.html">
- <Link>Resources\dashboard-ui\bower_components\iron-demo-helpers\demo-snippet.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-demo-helpers\index.html">
- <Link>Resources\dashboard-ui\bower_components\iron-demo-helpers\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-demo-helpers\url-bar.html">
- <Link>Resources\dashboard-ui\bower_components\iron-demo-helpers\url-bar.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-demo-helpers\.github\ISSUE_TEMPLATE.md">
- <Link>Resources\dashboard-ui\bower_components\iron-demo-helpers\.github\ISSUE_TEMPLATE.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-demo-helpers\demo\index.html">
- <Link>Resources\dashboard-ui\bower_components\iron-demo-helpers\demo\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-demo-helpers\demo\url-bar.html">
- <Link>Resources\dashboard-ui\bower_components\iron-demo-helpers\demo\url-bar.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-demo-helpers\test\basic.html">
- <Link>Resources\dashboard-ui\bower_components\iron-demo-helpers\test\basic.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-demo-helpers\test\index.html">
- <Link>Resources\dashboard-ui\bower_components\iron-demo-helpers\test\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-flex-layout\.bower.json">
- <Link>Resources\dashboard-ui\bower_components\iron-flex-layout\.bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-flex-layout\.gitignore">
- <Link>Resources\dashboard-ui\bower_components\iron-flex-layout\.gitignore</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-flex-layout\.travis.yml">
- <Link>Resources\dashboard-ui\bower_components\iron-flex-layout\.travis.yml</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-flex-layout\CONTRIBUTING.md">
- <Link>Resources\dashboard-ui\bower_components\iron-flex-layout\CONTRIBUTING.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-flex-layout\README.md">
- <Link>Resources\dashboard-ui\bower_components\iron-flex-layout\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-flex-layout\bower.json">
- <Link>Resources\dashboard-ui\bower_components\iron-flex-layout\bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-flex-layout\index.html">
- <Link>Resources\dashboard-ui\bower_components\iron-flex-layout\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-flex-layout\iron-flex-layout-classes.html">
- <Link>Resources\dashboard-ui\bower_components\iron-flex-layout\iron-flex-layout-classes.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-flex-layout\iron-flex-layout.html">
- <Link>Resources\dashboard-ui\bower_components\iron-flex-layout\iron-flex-layout.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-flex-layout\classes\iron-flex-layout.html">
- <Link>Resources\dashboard-ui\bower_components\iron-flex-layout\classes\iron-flex-layout.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-flex-layout\classes\iron-shadow-flex-layout.html">
- <Link>Resources\dashboard-ui\bower_components\iron-flex-layout\classes\iron-shadow-flex-layout.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-flex-layout\demo\index.html">
- <Link>Resources\dashboard-ui\bower_components\iron-flex-layout\demo\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-flex-layout\test\index.html">
- <Link>Resources\dashboard-ui\bower_components\iron-flex-layout\test\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-flex-layout\test\iron-flex-layout-classes.html">
- <Link>Resources\dashboard-ui\bower_components\iron-flex-layout\test\iron-flex-layout-classes.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-flex-layout\test\iron-flex-layout.html">
- <Link>Resources\dashboard-ui\bower_components\iron-flex-layout\test\iron-flex-layout.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-form-element-behavior\.bower.json">
- <Link>Resources\dashboard-ui\bower_components\iron-form-element-behavior\.bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-form-element-behavior\.gitignore">
- <Link>Resources\dashboard-ui\bower_components\iron-form-element-behavior\.gitignore</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-form-element-behavior\.travis.yml">
- <Link>Resources\dashboard-ui\bower_components\iron-form-element-behavior\.travis.yml</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-form-element-behavior\CONTRIBUTING.md">
- <Link>Resources\dashboard-ui\bower_components\iron-form-element-behavior\CONTRIBUTING.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-form-element-behavior\README.md">
- <Link>Resources\dashboard-ui\bower_components\iron-form-element-behavior\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-form-element-behavior\bower.json">
- <Link>Resources\dashboard-ui\bower_components\iron-form-element-behavior\bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-form-element-behavior\index.html">
- <Link>Resources\dashboard-ui\bower_components\iron-form-element-behavior\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-form-element-behavior\iron-form-element-behavior.html">
- <Link>Resources\dashboard-ui\bower_components\iron-form-element-behavior\iron-form-element-behavior.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-form-element-behavior\demo\index.html">
- <Link>Resources\dashboard-ui\bower_components\iron-form-element-behavior\demo\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-form-element-behavior\demo\simple-element.html">
- <Link>Resources\dashboard-ui\bower_components\iron-form-element-behavior\demo\simple-element.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-form-element-behavior\demo\simple-form.html">
- <Link>Resources\dashboard-ui\bower_components\iron-form-element-behavior\demo\simple-form.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-form-element-behavior\test\basic.html">
- <Link>Resources\dashboard-ui\bower_components\iron-form-element-behavior\test\basic.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-form-element-behavior\test\index.html">
- <Link>Resources\dashboard-ui\bower_components\iron-form-element-behavior\test\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-form-element-behavior\test\simple-element.html">
- <Link>Resources\dashboard-ui\bower_components\iron-form-element-behavior\test\simple-element.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-form-element-behavior\test\simple-form.html">
- <Link>Resources\dashboard-ui\bower_components\iron-form-element-behavior\test\simple-form.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-icon\.bower.json">
- <Link>Resources\dashboard-ui\bower_components\iron-icon\.bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-icon\.gitignore">
- <Link>Resources\dashboard-ui\bower_components\iron-icon\.gitignore</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-icon\.travis.yml">
- <Link>Resources\dashboard-ui\bower_components\iron-icon\.travis.yml</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-icon\CONTRIBUTING.md">
- <Link>Resources\dashboard-ui\bower_components\iron-icon\CONTRIBUTING.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-icon\README.md">
- <Link>Resources\dashboard-ui\bower_components\iron-icon\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-icon\bower.json">
- <Link>Resources\dashboard-ui\bower_components\iron-icon\bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-icon\hero.svg">
- <Link>Resources\dashboard-ui\bower_components\iron-icon\hero.svg</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-icon\index.html">
- <Link>Resources\dashboard-ui\bower_components\iron-icon\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-icon\iron-icon.html">
- <Link>Resources\dashboard-ui\bower_components\iron-icon\iron-icon.html</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\headroomjs\.bower.json">
+ <Link>Resources\dashboard-ui\bower_components\headroomjs\.bower.json</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-icon\.github\ISSUE_TEMPLATE.md">
- <Link>Resources\dashboard-ui\bower_components\iron-icon\.github\ISSUE_TEMPLATE.md</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\headroomjs\LICENSE">
+ <Link>Resources\dashboard-ui\bower_components\headroomjs\LICENSE</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-icon\demo\async.html">
- <Link>Resources\dashboard-ui\bower_components\iron-icon\demo\async.html</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\headroomjs\README.md">
+ <Link>Resources\dashboard-ui\bower_components\headroomjs\README.md</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-icon\demo\index.html">
- <Link>Resources\dashboard-ui\bower_components\iron-icon\demo\index.html</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\headroomjs\bower.json">
+ <Link>Resources\dashboard-ui\bower_components\headroomjs\bower.json</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-icon\demo\location.png">
- <Link>Resources\dashboard-ui\bower_components\iron-icon\demo\location.png</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\headroomjs\package.json">
+ <Link>Resources\dashboard-ui\bower_components\headroomjs\package.json</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-icon\test\index.html">
- <Link>Resources\dashboard-ui\bower_components\iron-icon\test\index.html</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\headroomjs\dist\angular.headroom.js">
+ <Link>Resources\dashboard-ui\bower_components\headroomjs\dist\angular.headroom.js</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-icon\test\iron-icon.html">
- <Link>Resources\dashboard-ui\bower_components\iron-icon\test\iron-icon.html</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\headroomjs\dist\angular.headroom.min.js">
+ <Link>Resources\dashboard-ui\bower_components\headroomjs\dist\angular.headroom.min.js</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-icons\.bower.json">
- <Link>Resources\dashboard-ui\bower_components\iron-icons\.bower.json</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\headroomjs\dist\headroom.js">
+ <Link>Resources\dashboard-ui\bower_components\headroomjs\dist\headroom.js</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-icons\.gitignore">
- <Link>Resources\dashboard-ui\bower_components\iron-icons\.gitignore</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\headroomjs\dist\headroom.min.js">
+ <Link>Resources\dashboard-ui\bower_components\headroomjs\dist\headroom.min.js</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-icons\.travis.yml">
- <Link>Resources\dashboard-ui\bower_components\iron-icons\.travis.yml</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\headroomjs\dist\jQuery.headroom.js">
+ <Link>Resources\dashboard-ui\bower_components\headroomjs\dist\jQuery.headroom.js</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-icons\CONTRIBUTING.md">
- <Link>Resources\dashboard-ui\bower_components\iron-icons\CONTRIBUTING.md</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\headroomjs\dist\jQuery.headroom.min.js">
+ <Link>Resources\dashboard-ui\bower_components\headroomjs\dist\jQuery.headroom.min.js</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-icons\README.md">
- <Link>Resources\dashboard-ui\bower_components\iron-icons\README.md</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\hlsjs\.bower.json">
+ <Link>Resources\dashboard-ui\bower_components\hlsjs\.bower.json</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-icons\av-icons.html">
- <Link>Resources\dashboard-ui\bower_components\iron-icons\av-icons.html</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\hlsjs\API.md">
+ <Link>Resources\dashboard-ui\bower_components\hlsjs\API.md</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-icons\bower.json">
- <Link>Resources\dashboard-ui\bower_components\iron-icons\bower.json</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\hlsjs\LICENSE">
+ <Link>Resources\dashboard-ui\bower_components\hlsjs\LICENSE</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-icons\communication-icons.html">
- <Link>Resources\dashboard-ui\bower_components\iron-icons\communication-icons.html</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\hlsjs\README.md">
+ <Link>Resources\dashboard-ui\bower_components\hlsjs\README.md</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-icons\device-icons.html">
- <Link>Resources\dashboard-ui\bower_components\iron-icons\device-icons.html</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\hlsjs\bower.json">
+ <Link>Resources\dashboard-ui\bower_components\hlsjs\bower.json</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-icons\editor-icons.html">
- <Link>Resources\dashboard-ui\bower_components\iron-icons\editor-icons.html</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\hlsjs\design.md">
+ <Link>Resources\dashboard-ui\bower_components\hlsjs\design.md</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-icons\hardware-icons.html">
- <Link>Resources\dashboard-ui\bower_components\iron-icons\hardware-icons.html</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\hlsjs\hls.js.sublime-project">
+ <Link>Resources\dashboard-ui\bower_components\hlsjs\hls.js.sublime-project</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-icons\hero.svg">
- <Link>Resources\dashboard-ui\bower_components\iron-icons\hero.svg</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\hlsjs\package.json">
+ <Link>Resources\dashboard-ui\bower_components\hlsjs\package.json</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-icons\image-icons.html">
- <Link>Resources\dashboard-ui\bower_components\iron-icons\image-icons.html</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\hlsjs\dist\hls.min.js">
+ <Link>Resources\dashboard-ui\bower_components\hlsjs\dist\hls.min.js</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-icons\index.html">
- <Link>Resources\dashboard-ui\bower_components\iron-icons\index.html</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\howlerjs\.bower.json">
+ <Link>Resources\dashboard-ui\bower_components\howlerjs\.bower.json</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-icons\iron-icons.html">
- <Link>Resources\dashboard-ui\bower_components\iron-icons\iron-icons.html</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\howlerjs\CHANGELOG.md">
+ <Link>Resources\dashboard-ui\bower_components\howlerjs\CHANGELOG.md</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-icons\maps-icons.html">
- <Link>Resources\dashboard-ui\bower_components\iron-icons\maps-icons.html</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\howlerjs\LICENSE.md">
+ <Link>Resources\dashboard-ui\bower_components\howlerjs\LICENSE.md</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-icons\notification-icons.html">
- <Link>Resources\dashboard-ui\bower_components\iron-icons\notification-icons.html</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\howlerjs\README.md">
+ <Link>Resources\dashboard-ui\bower_components\howlerjs\README.md</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-icons\places-icons.html">
- <Link>Resources\dashboard-ui\bower_components\iron-icons\places-icons.html</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\howlerjs\bower.json">
+ <Link>Resources\dashboard-ui\bower_components\howlerjs\bower.json</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-icons\social-icons.html">
- <Link>Resources\dashboard-ui\bower_components\iron-icons\social-icons.html</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\howlerjs\howler.min.js">
+ <Link>Resources\dashboard-ui\bower_components\howlerjs\howler.min.js</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-icons\demo\index.html">
- <Link>Resources\dashboard-ui\bower_components\iron-icons\demo\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-iconset-svg\.bower.json">
- <Link>Resources\dashboard-ui\bower_components\iron-iconset-svg\.bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-iconset-svg\.gitignore">
- <Link>Resources\dashboard-ui\bower_components\iron-iconset-svg\.gitignore</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-iconset-svg\.travis.yml">
- <Link>Resources\dashboard-ui\bower_components\iron-iconset-svg\.travis.yml</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-iconset-svg\CONTRIBUTING.md">
- <Link>Resources\dashboard-ui\bower_components\iron-iconset-svg\CONTRIBUTING.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-iconset-svg\README.md">
- <Link>Resources\dashboard-ui\bower_components\iron-iconset-svg\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-iconset-svg\bower.json">
- <Link>Resources\dashboard-ui\bower_components\iron-iconset-svg\bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-iconset-svg\index.html">
- <Link>Resources\dashboard-ui\bower_components\iron-iconset-svg\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-iconset-svg\iron-iconset-svg.html">
- <Link>Resources\dashboard-ui\bower_components\iron-iconset-svg\iron-iconset-svg.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-iconset-svg\demo\index.html">
- <Link>Resources\dashboard-ui\bower_components\iron-iconset-svg\demo\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-iconset-svg\demo\svg-sample-icons.html">
- <Link>Resources\dashboard-ui\bower_components\iron-iconset-svg\demo\svg-sample-icons.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-iconset-svg\test\index.html">
- <Link>Resources\dashboard-ui\bower_components\iron-iconset-svg\test\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-iconset-svg\test\iron-iconset-svg.html">
- <Link>Resources\dashboard-ui\bower_components\iron-iconset-svg\test\iron-iconset-svg.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-meta\.bower.json">
- <Link>Resources\dashboard-ui\bower_components\iron-meta\.bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-meta\.gitignore">
- <Link>Resources\dashboard-ui\bower_components\iron-meta\.gitignore</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-meta\.travis.yml">
- <Link>Resources\dashboard-ui\bower_components\iron-meta\.travis.yml</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-meta\CONTRIBUTING.md">
- <Link>Resources\dashboard-ui\bower_components\iron-meta\CONTRIBUTING.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-meta\README.md">
- <Link>Resources\dashboard-ui\bower_components\iron-meta\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-meta\bower.json">
- <Link>Resources\dashboard-ui\bower_components\iron-meta\bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-meta\hero.svg">
- <Link>Resources\dashboard-ui\bower_components\iron-meta\hero.svg</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-meta\index.html">
- <Link>Resources\dashboard-ui\bower_components\iron-meta\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-meta\iron-meta.html">
- <Link>Resources\dashboard-ui\bower_components\iron-meta\iron-meta.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-meta\.github\ISSUE_TEMPLATE.md">
- <Link>Resources\dashboard-ui\bower_components\iron-meta\.github\ISSUE_TEMPLATE.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-meta\demo\index.html">
- <Link>Resources\dashboard-ui\bower_components\iron-meta\demo\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-meta\test\basic.html">
- <Link>Resources\dashboard-ui\bower_components\iron-meta\test\basic.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-meta\test\index.html">
- <Link>Resources\dashboard-ui\bower_components\iron-meta\test\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-meta\test\iron-meta.html">
- <Link>Resources\dashboard-ui\bower_components\iron-meta\test\iron-meta.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-validatable-behavior\.bower.json">
- <Link>Resources\dashboard-ui\bower_components\iron-validatable-behavior\.bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-validatable-behavior\.gitignore">
- <Link>Resources\dashboard-ui\bower_components\iron-validatable-behavior\.gitignore</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-validatable-behavior\.travis.yml">
- <Link>Resources\dashboard-ui\bower_components\iron-validatable-behavior\.travis.yml</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-validatable-behavior\CONTRIBUTING.md">
- <Link>Resources\dashboard-ui\bower_components\iron-validatable-behavior\CONTRIBUTING.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-validatable-behavior\README.md">
- <Link>Resources\dashboard-ui\bower_components\iron-validatable-behavior\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-validatable-behavior\bower.json">
- <Link>Resources\dashboard-ui\bower_components\iron-validatable-behavior\bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-validatable-behavior\index.html">
- <Link>Resources\dashboard-ui\bower_components\iron-validatable-behavior\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-validatable-behavior\iron-validatable-behavior.html">
- <Link>Resources\dashboard-ui\bower_components\iron-validatable-behavior\iron-validatable-behavior.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-validatable-behavior\.github\ISSUE_TEMPLATE.md">
- <Link>Resources\dashboard-ui\bower_components\iron-validatable-behavior\.github\ISSUE_TEMPLATE.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-validatable-behavior\demo\cats-only.html">
- <Link>Resources\dashboard-ui\bower_components\iron-validatable-behavior\demo\cats-only.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-validatable-behavior\demo\index.html">
- <Link>Resources\dashboard-ui\bower_components\iron-validatable-behavior\demo\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-validatable-behavior\demo\validatable-input.html">
- <Link>Resources\dashboard-ui\bower_components\iron-validatable-behavior\demo\validatable-input.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-validatable-behavior\test\cats-only.html">
- <Link>Resources\dashboard-ui\bower_components\iron-validatable-behavior\test\cats-only.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-validatable-behavior\test\dogs-only.html">
- <Link>Resources\dashboard-ui\bower_components\iron-validatable-behavior\test\dogs-only.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-validatable-behavior\test\index.html">
- <Link>Resources\dashboard-ui\bower_components\iron-validatable-behavior\test\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-validatable-behavior\test\iron-validatable-behavior.html">
- <Link>Resources\dashboard-ui\bower_components\iron-validatable-behavior\test\iron-validatable-behavior.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-validatable-behavior\test\test-validatable.html">
- <Link>Resources\dashboard-ui\bower_components\iron-validatable-behavior\test\test-validatable.html</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\howlerjs\package.json">
+ <Link>Resources\dashboard-ui\bower_components\howlerjs\package.json</Link>
</BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\.bower.json">
<Link>Resources\dashboard-ui\bower_components\jquery\.bower.json</Link>
@@ -2622,8 +2142,8 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\external\sizzle\dist\sizzle.min.map">
<Link>Resources\dashboard-ui\bower_components\jquery\external\sizzle\dist\sizzle.min.map</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\.eslintrc">
- <Link>Resources\dashboard-ui\bower_components\jquery\src\.eslintrc</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\.eslintrc.json">
+ <Link>Resources\dashboard-ui\bower_components\jquery\src\.eslintrc.json</Link>
</BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\ajax.js">
<Link>Resources\dashboard-ui\bower_components\jquery\src\ajax.js</Link>
@@ -2739,6 +2259,9 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\core\readyException.js">
<Link>Resources\dashboard-ui\bower_components\jquery\src\core\readyException.js</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\core\stripAndCollapse.js">
+ <Link>Resources\dashboard-ui\bower_components\jquery\src\core\stripAndCollapse.js</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\core\support.js">
<Link>Resources\dashboard-ui\bower_components\jquery\src\core\support.js</Link>
</BundleResource>
@@ -3000,267 +2523,6 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\native-promise-only\lib\npo.src.js">
<Link>Resources\dashboard-ui\bower_components\native-promise-only\lib\npo.src.js</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-behaviors\.bower.json">
- <Link>Resources\dashboard-ui\bower_components\paper-behaviors\.bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-behaviors\.gitignore">
- <Link>Resources\dashboard-ui\bower_components\paper-behaviors\.gitignore</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-behaviors\.travis.yml">
- <Link>Resources\dashboard-ui\bower_components\paper-behaviors\.travis.yml</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-behaviors\CONTRIBUTING.md">
- <Link>Resources\dashboard-ui\bower_components\paper-behaviors\CONTRIBUTING.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-behaviors\README.md">
- <Link>Resources\dashboard-ui\bower_components\paper-behaviors\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-behaviors\bower.json">
- <Link>Resources\dashboard-ui\bower_components\paper-behaviors\bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-behaviors\index.html">
- <Link>Resources\dashboard-ui\bower_components\paper-behaviors\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-behaviors\paper-button-behavior.html">
- <Link>Resources\dashboard-ui\bower_components\paper-behaviors\paper-button-behavior.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-behaviors\paper-checked-element-behavior.html">
- <Link>Resources\dashboard-ui\bower_components\paper-behaviors\paper-checked-element-behavior.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-behaviors\paper-inky-focus-behavior.html">
- <Link>Resources\dashboard-ui\bower_components\paper-behaviors\paper-inky-focus-behavior.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-behaviors\paper-ripple-behavior.html">
- <Link>Resources\dashboard-ui\bower_components\paper-behaviors\paper-ripple-behavior.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-behaviors\.github\ISSUE_TEMPLATE.md">
- <Link>Resources\dashboard-ui\bower_components\paper-behaviors\.github\ISSUE_TEMPLATE.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-behaviors\demo\index.html">
- <Link>Resources\dashboard-ui\bower_components\paper-behaviors\demo\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-behaviors\demo\paper-button.html">
- <Link>Resources\dashboard-ui\bower_components\paper-behaviors\demo\paper-button.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-behaviors\demo\paper-radio-button.html">
- <Link>Resources\dashboard-ui\bower_components\paper-behaviors\demo\paper-radio-button.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-behaviors\test\index.html">
- <Link>Resources\dashboard-ui\bower_components\paper-behaviors\test\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-behaviors\test\paper-button-behavior.html">
- <Link>Resources\dashboard-ui\bower_components\paper-behaviors\test\paper-button-behavior.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-behaviors\test\paper-checked-element-behavior.html">
- <Link>Resources\dashboard-ui\bower_components\paper-behaviors\test\paper-checked-element-behavior.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-behaviors\test\paper-radio-button-behavior.html">
- <Link>Resources\dashboard-ui\bower_components\paper-behaviors\test\paper-radio-button-behavior.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-behaviors\test\paper-ripple-behavior.html">
- <Link>Resources\dashboard-ui\bower_components\paper-behaviors\test\paper-ripple-behavior.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-behaviors\test\shadowed-ripple.html">
- <Link>Resources\dashboard-ui\bower_components\paper-behaviors\test\shadowed-ripple.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-behaviors\test\test-button.html">
- <Link>Resources\dashboard-ui\bower_components\paper-behaviors\test\test-button.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-behaviors\test\test-radio-button.html">
- <Link>Resources\dashboard-ui\bower_components\paper-behaviors\test\test-radio-button.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-checkbox\.bower.json">
- <Link>Resources\dashboard-ui\bower_components\paper-checkbox\.bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-checkbox\.gitignore">
- <Link>Resources\dashboard-ui\bower_components\paper-checkbox\.gitignore</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-checkbox\.travis.yml">
- <Link>Resources\dashboard-ui\bower_components\paper-checkbox\.travis.yml</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-checkbox\CONTRIBUTING.md">
- <Link>Resources\dashboard-ui\bower_components\paper-checkbox\CONTRIBUTING.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-checkbox\README.md">
- <Link>Resources\dashboard-ui\bower_components\paper-checkbox\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-checkbox\bower.json">
- <Link>Resources\dashboard-ui\bower_components\paper-checkbox\bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-checkbox\index.html">
- <Link>Resources\dashboard-ui\bower_components\paper-checkbox\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-checkbox\metadata.html">
- <Link>Resources\dashboard-ui\bower_components\paper-checkbox\metadata.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-checkbox\paper-checkbox.html">
- <Link>Resources\dashboard-ui\bower_components\paper-checkbox\paper-checkbox.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-checkbox\demo\index.html">
- <Link>Resources\dashboard-ui\bower_components\paper-checkbox\demo\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-checkbox\test\basic.html">
- <Link>Resources\dashboard-ui\bower_components\paper-checkbox\test\basic.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-checkbox\test\index.html">
- <Link>Resources\dashboard-ui\bower_components\paper-checkbox\test\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-icon-button\.bower.json">
- <Link>Resources\dashboard-ui\bower_components\paper-icon-button\.bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-icon-button\.gitignore">
- <Link>Resources\dashboard-ui\bower_components\paper-icon-button\.gitignore</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-icon-button\.travis.yml">
- <Link>Resources\dashboard-ui\bower_components\paper-icon-button\.travis.yml</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-icon-button\CONTRIBUTING.md">
- <Link>Resources\dashboard-ui\bower_components\paper-icon-button\CONTRIBUTING.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-icon-button\README.md">
- <Link>Resources\dashboard-ui\bower_components\paper-icon-button\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-icon-button\bower.json">
- <Link>Resources\dashboard-ui\bower_components\paper-icon-button\bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-icon-button\index.html">
- <Link>Resources\dashboard-ui\bower_components\paper-icon-button\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-icon-button\paper-icon-button-light.html">
- <Link>Resources\dashboard-ui\bower_components\paper-icon-button\paper-icon-button-light.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-icon-button\paper-icon-button.html">
- <Link>Resources\dashboard-ui\bower_components\paper-icon-button\paper-icon-button.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-icon-button\.github\ISSUE_TEMPLATE.md">
- <Link>Resources\dashboard-ui\bower_components\paper-icon-button\.github\ISSUE_TEMPLATE.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-icon-button\demo\index.html">
- <Link>Resources\dashboard-ui\bower_components\paper-icon-button\demo\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-icon-button\demo\paper-icon-button-light.html">
- <Link>Resources\dashboard-ui\bower_components\paper-icon-button\demo\paper-icon-button-light.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-icon-button\test\a11y.html">
- <Link>Resources\dashboard-ui\bower_components\paper-icon-button\test\a11y.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-icon-button\test\basic.html">
- <Link>Resources\dashboard-ui\bower_components\paper-icon-button\test\basic.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-icon-button\test\index.html">
- <Link>Resources\dashboard-ui\bower_components\paper-icon-button\test\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-ripple\.bower.json">
- <Link>Resources\dashboard-ui\bower_components\paper-ripple\.bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-ripple\.gitignore">
- <Link>Resources\dashboard-ui\bower_components\paper-ripple\.gitignore</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-ripple\.travis.yml">
- <Link>Resources\dashboard-ui\bower_components\paper-ripple\.travis.yml</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-ripple\CONTRIBUTING.md">
- <Link>Resources\dashboard-ui\bower_components\paper-ripple\CONTRIBUTING.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-ripple\README.md">
- <Link>Resources\dashboard-ui\bower_components\paper-ripple\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-ripple\bower.json">
- <Link>Resources\dashboard-ui\bower_components\paper-ripple\bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-ripple\hero.svg">
- <Link>Resources\dashboard-ui\bower_components\paper-ripple\hero.svg</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-ripple\index.html">
- <Link>Resources\dashboard-ui\bower_components\paper-ripple\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-ripple\paper-ripple.html">
- <Link>Resources\dashboard-ui\bower_components\paper-ripple\paper-ripple.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-ripple\.github\ISSUE_TEMPLATE.md">
- <Link>Resources\dashboard-ui\bower_components\paper-ripple\.github\ISSUE_TEMPLATE.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-ripple\demo\index.html">
- <Link>Resources\dashboard-ui\bower_components\paper-ripple\demo\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-ripple\test\index.html">
- <Link>Resources\dashboard-ui\bower_components\paper-ripple\test\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-ripple\test\paper-ripple.html">
- <Link>Resources\dashboard-ui\bower_components\paper-ripple\test\paper-ripple.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-styles\.bower.json">
- <Link>Resources\dashboard-ui\bower_components\paper-styles\.bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-styles\CONTRIBUTING.md">
- <Link>Resources\dashboard-ui\bower_components\paper-styles\CONTRIBUTING.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-styles\README.md">
- <Link>Resources\dashboard-ui\bower_components\paper-styles\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-styles\bower.json">
- <Link>Resources\dashboard-ui\bower_components\paper-styles\bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-styles\color.html">
- <Link>Resources\dashboard-ui\bower_components\paper-styles\color.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-styles\default-theme.html">
- <Link>Resources\dashboard-ui\bower_components\paper-styles\default-theme.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-styles\demo-pages.html">
- <Link>Resources\dashboard-ui\bower_components\paper-styles\demo-pages.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-styles\demo.css">
- <Link>Resources\dashboard-ui\bower_components\paper-styles\demo.css</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-styles\index.html">
- <Link>Resources\dashboard-ui\bower_components\paper-styles\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-styles\paper-styles-classes.html">
- <Link>Resources\dashboard-ui\bower_components\paper-styles\paper-styles-classes.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-styles\paper-styles.html">
- <Link>Resources\dashboard-ui\bower_components\paper-styles\paper-styles.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-styles\shadow.html">
- <Link>Resources\dashboard-ui\bower_components\paper-styles\shadow.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-styles\typography.html">
- <Link>Resources\dashboard-ui\bower_components\paper-styles\typography.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-styles\classes\global.html">
- <Link>Resources\dashboard-ui\bower_components\paper-styles\classes\global.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-styles\classes\shadow-layout.html">
- <Link>Resources\dashboard-ui\bower_components\paper-styles\classes\shadow-layout.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-styles\classes\shadow.html">
- <Link>Resources\dashboard-ui\bower_components\paper-styles\classes\shadow.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-styles\classes\typography.html">
- <Link>Resources\dashboard-ui\bower_components\paper-styles\classes\typography.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-styles\demo\index.html">
- <Link>Resources\dashboard-ui\bower_components\paper-styles\demo\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\polymer\.bower.json">
- <Link>Resources\dashboard-ui\bower_components\polymer\.bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\polymer\LICENSE.txt">
- <Link>Resources\dashboard-ui\bower_components\polymer\LICENSE.txt</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\polymer\bower.json">
- <Link>Resources\dashboard-ui\bower_components\polymer\bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\polymer\polymer-micro.html">
- <Link>Resources\dashboard-ui\bower_components\polymer\polymer-micro.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\polymer\polymer-mini.html">
- <Link>Resources\dashboard-ui\bower_components\polymer\polymer-mini.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\polymer\polymer.html">
- <Link>Resources\dashboard-ui\bower_components\polymer\polymer.html</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\query-string\.bower.json">
<Link>Resources\dashboard-ui\bower_components\query-string\.bower.json</Link>
</BundleResource>
@@ -3318,6 +2580,39 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\requirejs\require.js">
<Link>Resources\dashboard-ui\bower_components\requirejs\require.js</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\vibrant\.bower.json">
+ <Link>Resources\dashboard-ui\bower_components\vibrant\.bower.json</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\vibrant\LICENSE.md">
+ <Link>Resources\dashboard-ui\bower_components\vibrant\LICENSE.md</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\vibrant\README.md">
+ <Link>Resources\dashboard-ui\bower_components\vibrant\README.md</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\vibrant\bower.json">
+ <Link>Resources\dashboard-ui\bower_components\vibrant\bower.json</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\vibrant\gulpfile.coffee">
+ <Link>Resources\dashboard-ui\bower_components\vibrant\gulpfile.coffee</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\vibrant\index.html">
+ <Link>Resources\dashboard-ui\bower_components\vibrant\index.html</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\vibrant\package.json">
+ <Link>Resources\dashboard-ui\bower_components\vibrant\package.json</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\vibrant\style.css">
+ <Link>Resources\dashboard-ui\bower_components\vibrant\style.css</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\vibrant\website.coffee">
+ <Link>Resources\dashboard-ui\bower_components\vibrant\website.coffee</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\vibrant\dist\Vibrant.js">
+ <Link>Resources\dashboard-ui\bower_components\vibrant\dist\Vibrant.js</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\vibrant\dist\vibrant.min.js">
+ <Link>Resources\dashboard-ui\bower_components\vibrant\dist\vibrant.min.js</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\web-animations-js\.bower.json">
<Link>Resources\dashboard-ui\bower_components\web-animations-js\.bower.json</Link>
</BundleResource>
@@ -3348,21 +2643,9 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\webcomponentsjs\.bower.json">
<Link>Resources\dashboard-ui\bower_components\webcomponentsjs\.bower.json</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\webcomponentsjs\CustomElements.min.js">
- <Link>Resources\dashboard-ui\bower_components\webcomponentsjs\CustomElements.min.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\webcomponentsjs\HTMLImports.min.js">
- <Link>Resources\dashboard-ui\bower_components\webcomponentsjs\HTMLImports.min.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\webcomponentsjs\MutationObserver.min.js">
- <Link>Resources\dashboard-ui\bower_components\webcomponentsjs\MutationObserver.min.js</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\webcomponentsjs\README.md">
<Link>Resources\dashboard-ui\bower_components\webcomponentsjs\README.md</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\webcomponentsjs\ShadowDOM.min.js">
- <Link>Resources\dashboard-ui\bower_components\webcomponentsjs\ShadowDOM.min.js</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\webcomponentsjs\bower.json">
<Link>Resources\dashboard-ui\bower_components\webcomponentsjs\bower.json</Link>
</BundleResource>
@@ -3372,9 +2655,6 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\webcomponentsjs\webcomponents-lite.min.js">
<Link>Resources\dashboard-ui\bower_components\webcomponentsjs\webcomponents-lite.min.js</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\webcomponentsjs\webcomponents.min.js">
- <Link>Resources\dashboard-ui\bower_components\webcomponentsjs\webcomponents.min.js</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\apphost.js">
<Link>Resources\dashboard-ui\components\apphost.js</Link>
</BundleResource>
@@ -3402,6 +2682,12 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\viewcontainer-lite.js">
<Link>Resources\dashboard-ui\components\viewcontainer-lite.js</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\accessschedule\accessschedule.js">
+ <Link>Resources\dashboard-ui\components\accessschedule\accessschedule.js</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\accessschedule\accessschedule.template.html">
+ <Link>Resources\dashboard-ui\components\accessschedule\accessschedule.template.html</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\appfooter\appfooter.css">
<Link>Resources\dashboard-ui\components\appfooter\appfooter.css</Link>
</BundleResource>
@@ -4110,9 +3396,6 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\pluginspage.js">
<Link>Resources\dashboard-ui\scripts\pluginspage.js</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\ratingdialog.js">
- <Link>Resources\dashboard-ui\scripts\ratingdialog.js</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\registrationservices.js">
<Link>Resources\dashboard-ui\scripts\registrationservices.js</Link>
</BundleResource>
@@ -4377,21 +3660,6 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\thirdparty\paper-button-style.css">
<Link>Resources\dashboard-ui\thirdparty\paper-button-style.css</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.checkbox.css">
- <Link>Resources\dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.checkbox.css</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.checkbox.js">
- <Link>Resources\dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.checkbox.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.collapsible.css">
- <Link>Resources\dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.collapsible.css</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.collapsible.js">
- <Link>Resources\dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.collapsible.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.controlgroup.css">
- <Link>Resources\dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.controlgroup.css</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.listview.css">
<Link>Resources\dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.listview.css</Link>
</BundleResource>
@@ -4422,12 +3690,6 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.widget.js">
<Link>Resources\dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.widget.js</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\thirdparty\jquerymobile-1.4.5\jquery.mobile.custom.icons.css">
- <Link>Resources\dashboard-ui\thirdparty\jquerymobile-1.4.5\jquery.mobile.custom.icons.css</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\thirdparty\jquerymobile-1.4.5\jquery.mobile.custom.theme.css">
- <Link>Resources\dashboard-ui\thirdparty\jquerymobile-1.4.5\jquery.mobile.custom.theme.css</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\thirdparty\jstree\themes\default\32px.png">
<Link>Resources\dashboard-ui\thirdparty\jstree\themes\default\32px.png</Link>
</BundleResource>
diff --git a/MediaBrowser.Server.Mac/Native/BaseMonoApp.cs b/MediaBrowser.Server.Mac/Native/BaseMonoApp.cs
index f4e054406..f28ff8c0d 100644
--- a/MediaBrowser.Server.Mac/Native/BaseMonoApp.cs
+++ b/MediaBrowser.Server.Mac/Native/BaseMonoApp.cs
@@ -6,7 +6,6 @@ using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text.RegularExpressions;
-using MediaBrowser.Controller.Power;
using MediaBrowser.Server.Implementations.Persistence;
using MediaBrowser.Server.Startup.Common.FFMpeg;
using System.Diagnostics;
@@ -200,11 +199,6 @@ namespace MediaBrowser.Server.Mac
return new NetworkManager(logger);
}
- public IPowerManagement GetPowerManagement()
- {
- return new NullPowerManagement ();
- }
-
public void EnableLoopback(string appName)
{
@@ -274,13 +268,5 @@ namespace MediaBrowser.Server.Mac
public string sysname = string.Empty;
public string machine = string.Empty;
}
-
- private class NullPowerManagement : IPowerManagement
- {
- public void ScheduleWake(DateTime utcTime)
- {
- throw new NotImplementedException ();
- }
- }
}
}
diff --git a/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj b/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj
index bcbb10174..e7acb3f50 100644
--- a/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj
+++ b/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
@@ -10,8 +10,9 @@
<RootNamespace>MediaBrowser.Server.Mono</RootNamespace>
<AssemblyName>MediaBrowser.Server.Mono</AssemblyName>
<StartupObject>MediaBrowser.Server.Mono.MainClass</StartupObject>
- <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ <TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
+ <TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
<DebugSymbols>true</DebugSymbols>
diff --git a/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs b/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs
index 48f6a2a48..73e2686d2 100644
--- a/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs
+++ b/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs
@@ -8,7 +8,6 @@ using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text.RegularExpressions;
-using MediaBrowser.Controller.Power;
using MediaBrowser.Model.System;
using MediaBrowser.Server.Implementations.Persistence;
using MediaBrowser.Server.Startup.Common.FFMpeg;
@@ -232,11 +231,6 @@ namespace MediaBrowser.Server.Mono.Native
public string machine = string.Empty;
}
- public IPowerManagement GetPowerManagement()
- {
- return new NullPowerManagement();
- }
-
public FFMpegInstallInfo GetFfmpegInstallInfo()
{
return GetInfo(Environment);
@@ -283,18 +277,5 @@ namespace MediaBrowser.Server.Mono.Native
{
}
-
- public bool PortsRequireAuthorization(string applicationPath)
- {
- return false;
- }
- }
-
- public class NullPowerManagement : IPowerManagement
- {
- public void ScheduleWake(DateTime utcTime)
- {
- throw new NotImplementedException();
- }
}
}
diff --git a/MediaBrowser.Server.Mono/app.config b/MediaBrowser.Server.Mono/app.config
index b0e8558fd..e14b908ad 100644
--- a/MediaBrowser.Server.Mono/app.config
+++ b/MediaBrowser.Server.Mono/app.config
@@ -1,21 +1,21 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
- <section name="nlog" type="NLog.Config.ConfigSectionHandler, NLog" />
+ <section name="nlog" type="NLog.Config.ConfigSectionHandler, NLog"/>
</configSections>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<targets async="true"></targets>
</nlog>
<appSettings>
- <add key="DebugProgramDataPath" value="ProgramData-Server" />
- <add key="ReleaseProgramDataPath" value="ProgramData-Server" />
+ <add key="DebugProgramDataPath" value="ProgramData-Server"/>
+ <add key="ReleaseProgramDataPath" value="ProgramData-Server"/>
</appSettings>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
- <assemblyIdentity name="System.Data.SQLite" publicKeyToken="db937bc2d44ff139" culture="neutral" />
- <bindingRedirect oldVersion="0.0.0.0-1.0.94.0" newVersion="1.0.94.0" />
+ <assemblyIdentity name="System.Data.SQLite" publicKeyToken="db937bc2d44ff139" culture="neutral"/>
+ <bindingRedirect oldVersion="0.0.0.0-1.0.94.0" newVersion="1.0.94.0"/>
</dependentAssembly>
</assemblyBinding>
</runtime>
-</configuration>
+<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.1"/></startup></configuration>
diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs
index 49013131e..5439b1b81 100644
--- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs
+++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs
@@ -102,7 +102,12 @@ using System.Threading;
using System.Threading.Tasks;
using CommonIO;
using MediaBrowser.Api.Playback;
+using MediaBrowser.Common.Implementations.Serialization;
using MediaBrowser.Common.Implementations.Updates;
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Model.Serialization;
namespace MediaBrowser.Server.Startup.Common
{
@@ -366,6 +371,195 @@ namespace MediaBrowser.Server.Startup.Common
LogManager.RemoveConsoleOutput();
}
+ protected override IJsonSerializer CreateJsonSerializer()
+ {
+ var result = base.CreateJsonSerializer();
+
+ ServiceStack.Text.JsConfig<Movie>.ExcludePropertyNames = new[] { "ShortOverview" };
+ ServiceStack.Text.JsConfig<Movie>.ExcludePropertyNames = new[] { "Taglines" };
+ ServiceStack.Text.JsConfig<Movie>.ExcludePropertyNames = new[] { "Keywords" };
+ ServiceStack.Text.JsConfig<Trailer>.ExcludePropertyNames = new[] { "ShortOverview" };
+ ServiceStack.Text.JsConfig<Series>.ExcludePropertyNames = new[] { "ShortOverview" };
+ ServiceStack.Text.JsConfig<Person>.ExcludePropertyNames = new[] { "PlaceOfBirth" };
+
+ ServiceStack.Text.JsConfig<LiveTvProgram>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<LiveTvChannel>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<LiveTvVideoRecording>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<LiveTvAudioRecording>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<Series>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<Audio>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<MusicAlbum>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<MusicArtist>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<MusicGenre>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<MusicVideo>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<Movie>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<Playlist>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<AudioPodcast>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<Trailer>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<BoxSet>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<Episode>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<Season>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<Book>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<CollectionFolder>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<Folder>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<Game>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<GameGenre>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<GameSystem>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<Genre>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<Person>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<Photo>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<PhotoAlbum>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<Studio>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<UserRootFolder>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<UserView>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<Video>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<Year>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<Channel>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<AggregateFolder>.ExcludePropertyNames = new[] { "ProviderIds" };
+
+ ServiceStack.Text.JsConfig<LiveTvProgram>.ExcludePropertyNames = new[] { "ImageInfos" };
+ ServiceStack.Text.JsConfig<LiveTvChannel>.ExcludePropertyNames = new[] { "ImageInfos" };
+ ServiceStack.Text.JsConfig<LiveTvVideoRecording>.ExcludePropertyNames = new[] { "ImageInfos" };
+ ServiceStack.Text.JsConfig<LiveTvAudioRecording>.ExcludePropertyNames = new[] { "ImageInfos" };
+ ServiceStack.Text.JsConfig<Series>.ExcludePropertyNames = new[] { "ImageInfos" };
+ ServiceStack.Text.JsConfig<Audio>.ExcludePropertyNames = new[] { "ImageInfos" };
+ ServiceStack.Text.JsConfig<MusicAlbum>.ExcludePropertyNames = new[] { "ImageInfos" };
+ ServiceStack.Text.JsConfig<MusicArtist>.ExcludePropertyNames = new[] { "ImageInfos" };
+ ServiceStack.Text.JsConfig<MusicGenre>.ExcludePropertyNames = new[] { "ImageInfos" };
+ ServiceStack.Text.JsConfig<MusicVideo>.ExcludePropertyNames = new[] { "ImageInfos" };
+ ServiceStack.Text.JsConfig<Movie>.ExcludePropertyNames = new[] { "ImageInfos" };
+ ServiceStack.Text.JsConfig<Playlist>.ExcludePropertyNames = new[] { "ImageInfos" };
+ ServiceStack.Text.JsConfig<AudioPodcast>.ExcludePropertyNames = new[] { "ImageInfos" };
+ ServiceStack.Text.JsConfig<Trailer>.ExcludePropertyNames = new[] { "ImageInfos" };
+ ServiceStack.Text.JsConfig<BoxSet>.ExcludePropertyNames = new[] { "ImageInfos" };
+ ServiceStack.Text.JsConfig<Episode>.ExcludePropertyNames = new[] { "ImageInfos" };
+ ServiceStack.Text.JsConfig<Season>.ExcludePropertyNames = new[] { "ImageInfos" };
+ ServiceStack.Text.JsConfig<Book>.ExcludePropertyNames = new[] { "ImageInfos" };
+ ServiceStack.Text.JsConfig<CollectionFolder>.ExcludePropertyNames = new[] { "ImageInfos" };
+ ServiceStack.Text.JsConfig<Folder>.ExcludePropertyNames = new[] { "ImageInfos" };
+ ServiceStack.Text.JsConfig<Game>.ExcludePropertyNames = new[] { "ImageInfos" };
+ ServiceStack.Text.JsConfig<GameGenre>.ExcludePropertyNames = new[] { "ImageInfos" };
+ ServiceStack.Text.JsConfig<GameSystem>.ExcludePropertyNames = new[] { "ImageInfos" };
+ ServiceStack.Text.JsConfig<Genre>.ExcludePropertyNames = new[] { "ImageInfos" };
+ ServiceStack.Text.JsConfig<Person>.ExcludePropertyNames = new[] { "ImageInfos" };
+ ServiceStack.Text.JsConfig<Photo>.ExcludePropertyNames = new[] { "ImageInfos" };
+ ServiceStack.Text.JsConfig<PhotoAlbum>.ExcludePropertyNames = new[] { "ImageInfos" };
+ ServiceStack.Text.JsConfig<Studio>.ExcludePropertyNames = new[] { "ImageInfos" };
+ ServiceStack.Text.JsConfig<UserRootFolder>.ExcludePropertyNames = new[] { "ImageInfos" };
+ ServiceStack.Text.JsConfig<UserView>.ExcludePropertyNames = new[] { "ImageInfos" };
+ ServiceStack.Text.JsConfig<Video>.ExcludePropertyNames = new[] { "ImageInfos" };
+ ServiceStack.Text.JsConfig<Year>.ExcludePropertyNames = new[] { "ImageInfos" };
+ ServiceStack.Text.JsConfig<Channel>.ExcludePropertyNames = new[] { "ImageInfos" };
+ ServiceStack.Text.JsConfig<AggregateFolder>.ExcludePropertyNames = new[] { "ImageInfos" };
+
+ ServiceStack.Text.JsConfig<LiveTvProgram>.ExcludePropertyNames = new[] { "ProductionLocations" };
+ ServiceStack.Text.JsConfig<LiveTvChannel>.ExcludePropertyNames = new[] { "ProductionLocations" };
+ ServiceStack.Text.JsConfig<LiveTvVideoRecording>.ExcludePropertyNames = new[] { "ProductionLocations" };
+ ServiceStack.Text.JsConfig<LiveTvAudioRecording>.ExcludePropertyNames = new[] { "ProductionLocations" };
+ ServiceStack.Text.JsConfig<Series>.ExcludePropertyNames = new[] { "ProductionLocations" };
+ ServiceStack.Text.JsConfig<Audio>.ExcludePropertyNames = new[] { "ProductionLocations" };
+ ServiceStack.Text.JsConfig<MusicAlbum>.ExcludePropertyNames = new[] { "ProductionLocations" };
+ ServiceStack.Text.JsConfig<MusicArtist>.ExcludePropertyNames = new[] { "ProductionLocations" };
+ ServiceStack.Text.JsConfig<MusicGenre>.ExcludePropertyNames = new[] { "ProductionLocations" };
+ ServiceStack.Text.JsConfig<MusicVideo>.ExcludePropertyNames = new[] { "ProductionLocations" };
+ ServiceStack.Text.JsConfig<Movie>.ExcludePropertyNames = new[] { "ProductionLocations" };
+ ServiceStack.Text.JsConfig<Playlist>.ExcludePropertyNames = new[] { "ProductionLocations" };
+ ServiceStack.Text.JsConfig<AudioPodcast>.ExcludePropertyNames = new[] { "ProductionLocations" };
+ ServiceStack.Text.JsConfig<Trailer>.ExcludePropertyNames = new[] { "ProductionLocations" };
+ ServiceStack.Text.JsConfig<BoxSet>.ExcludePropertyNames = new[] { "ProductionLocations" };
+ ServiceStack.Text.JsConfig<Episode>.ExcludePropertyNames = new[] { "ProductionLocations" };
+ ServiceStack.Text.JsConfig<Season>.ExcludePropertyNames = new[] { "ProductionLocations" };
+ ServiceStack.Text.JsConfig<Book>.ExcludePropertyNames = new[] { "ProductionLocations" };
+ ServiceStack.Text.JsConfig<CollectionFolder>.ExcludePropertyNames = new[] { "ProductionLocations" };
+ ServiceStack.Text.JsConfig<Folder>.ExcludePropertyNames = new[] { "ProductionLocations" };
+ ServiceStack.Text.JsConfig<Game>.ExcludePropertyNames = new[] { "ProductionLocations" };
+ ServiceStack.Text.JsConfig<GameGenre>.ExcludePropertyNames = new[] { "ProductionLocations" };
+ ServiceStack.Text.JsConfig<GameSystem>.ExcludePropertyNames = new[] { "ProductionLocations" };
+ ServiceStack.Text.JsConfig<Genre>.ExcludePropertyNames = new[] { "ProductionLocations" };
+ ServiceStack.Text.JsConfig<Person>.ExcludePropertyNames = new[] { "ProductionLocations" };
+ ServiceStack.Text.JsConfig<Photo>.ExcludePropertyNames = new[] { "ProductionLocations" };
+ ServiceStack.Text.JsConfig<PhotoAlbum>.ExcludePropertyNames = new[] { "ProductionLocations" };
+ ServiceStack.Text.JsConfig<Studio>.ExcludePropertyNames = new[] { "ProductionLocations" };
+ ServiceStack.Text.JsConfig<UserRootFolder>.ExcludePropertyNames = new[] { "ProductionLocations" };
+ ServiceStack.Text.JsConfig<UserView>.ExcludePropertyNames = new[] { "ProductionLocations" };
+ ServiceStack.Text.JsConfig<Video>.ExcludePropertyNames = new[] { "ProductionLocations" };
+ ServiceStack.Text.JsConfig<Year>.ExcludePropertyNames = new[] { "ProductionLocations" };
+ ServiceStack.Text.JsConfig<Channel>.ExcludePropertyNames = new[] { "ProductionLocations" };
+ ServiceStack.Text.JsConfig<AggregateFolder>.ExcludePropertyNames = new[] { "ProductionLocations" };
+
+ ServiceStack.Text.JsConfig<LiveTvProgram>.ExcludePropertyNames = new[] { "ThemeSongIds" };
+ ServiceStack.Text.JsConfig<LiveTvChannel>.ExcludePropertyNames = new[] { "ThemeSongIds" };
+ ServiceStack.Text.JsConfig<LiveTvVideoRecording>.ExcludePropertyNames = new[] { "ThemeSongIds" };
+ ServiceStack.Text.JsConfig<LiveTvAudioRecording>.ExcludePropertyNames = new[] { "ThemeSongIds" };
+ ServiceStack.Text.JsConfig<Series>.ExcludePropertyNames = new[] { "ThemeSongIds" };
+ ServiceStack.Text.JsConfig<Audio>.ExcludePropertyNames = new[] { "ThemeSongIds" };
+ ServiceStack.Text.JsConfig<MusicAlbum>.ExcludePropertyNames = new[] { "ThemeSongIds" };
+ ServiceStack.Text.JsConfig<MusicArtist>.ExcludePropertyNames = new[] { "ThemeSongIds" };
+ ServiceStack.Text.JsConfig<MusicGenre>.ExcludePropertyNames = new[] { "ThemeSongIds" };
+ ServiceStack.Text.JsConfig<MusicVideo>.ExcludePropertyNames = new[] { "ThemeSongIds" };
+ ServiceStack.Text.JsConfig<Movie>.ExcludePropertyNames = new[] { "ThemeSongIds" };
+ ServiceStack.Text.JsConfig<Playlist>.ExcludePropertyNames = new[] { "ThemeSongIds" };
+ ServiceStack.Text.JsConfig<AudioPodcast>.ExcludePropertyNames = new[] { "ThemeSongIds" };
+ ServiceStack.Text.JsConfig<Trailer>.ExcludePropertyNames = new[] { "ThemeSongIds" };
+ ServiceStack.Text.JsConfig<BoxSet>.ExcludePropertyNames = new[] { "ThemeSongIds" };
+ ServiceStack.Text.JsConfig<Episode>.ExcludePropertyNames = new[] { "ThemeSongIds" };
+ ServiceStack.Text.JsConfig<Season>.ExcludePropertyNames = new[] { "ThemeSongIds" };
+ ServiceStack.Text.JsConfig<Book>.ExcludePropertyNames = new[] { "ThemeSongIds" };
+ ServiceStack.Text.JsConfig<CollectionFolder>.ExcludePropertyNames = new[] { "ThemeSongIds" };
+ ServiceStack.Text.JsConfig<Folder>.ExcludePropertyNames = new[] { "ThemeSongIds" };
+ ServiceStack.Text.JsConfig<Game>.ExcludePropertyNames = new[] { "ThemeSongIds" };
+ ServiceStack.Text.JsConfig<GameGenre>.ExcludePropertyNames = new[] { "ThemeSongIds" };
+ ServiceStack.Text.JsConfig<GameSystem>.ExcludePropertyNames = new[] { "ThemeSongIds" };
+ ServiceStack.Text.JsConfig<Genre>.ExcludePropertyNames = new[] { "ThemeSongIds" };
+ ServiceStack.Text.JsConfig<Person>.ExcludePropertyNames = new[] { "ThemeSongIds" };
+ ServiceStack.Text.JsConfig<Photo>.ExcludePropertyNames = new[] { "ThemeSongIds" };
+ ServiceStack.Text.JsConfig<PhotoAlbum>.ExcludePropertyNames = new[] { "ThemeSongIds" };
+ ServiceStack.Text.JsConfig<Studio>.ExcludePropertyNames = new[] { "ThemeSongIds" };
+ ServiceStack.Text.JsConfig<UserRootFolder>.ExcludePropertyNames = new[] { "ThemeSongIds" };
+ ServiceStack.Text.JsConfig<UserView>.ExcludePropertyNames = new[] { "ThemeSongIds" };
+ ServiceStack.Text.JsConfig<Video>.ExcludePropertyNames = new[] { "ThemeSongIds" };
+ ServiceStack.Text.JsConfig<Year>.ExcludePropertyNames = new[] { "ThemeSongIds" };
+ ServiceStack.Text.JsConfig<Channel>.ExcludePropertyNames = new[] { "ThemeSongIds" };
+ ServiceStack.Text.JsConfig<AggregateFolder>.ExcludePropertyNames = new[] { "ThemeSongIds" };
+
+ ServiceStack.Text.JsConfig<LiveTvProgram>.ExcludePropertyNames = new[] { "ThemeVideoIds" };
+ ServiceStack.Text.JsConfig<LiveTvChannel>.ExcludePropertyNames = new[] { "ThemeVideoIds" };
+ ServiceStack.Text.JsConfig<LiveTvVideoRecording>.ExcludePropertyNames = new[] { "ThemeVideoIds" };
+ ServiceStack.Text.JsConfig<LiveTvAudioRecording>.ExcludePropertyNames = new[] { "ThemeVideoIds" };
+ ServiceStack.Text.JsConfig<Series>.ExcludePropertyNames = new[] { "ThemeVideoIds" };
+ ServiceStack.Text.JsConfig<Audio>.ExcludePropertyNames = new[] { "ThemeVideoIds" };
+ ServiceStack.Text.JsConfig<MusicAlbum>.ExcludePropertyNames = new[] { "ThemeVideoIds" };
+ ServiceStack.Text.JsConfig<MusicArtist>.ExcludePropertyNames = new[] { "ThemeVideoIds" };
+ ServiceStack.Text.JsConfig<MusicGenre>.ExcludePropertyNames = new[] { "ThemeVideoIds" };
+ ServiceStack.Text.JsConfig<MusicVideo>.ExcludePropertyNames = new[] { "ThemeVideoIds" };
+ ServiceStack.Text.JsConfig<Movie>.ExcludePropertyNames = new[] { "ThemeVideoIds" };
+ ServiceStack.Text.JsConfig<Playlist>.ExcludePropertyNames = new[] { "ThemeVideoIds" };
+ ServiceStack.Text.JsConfig<AudioPodcast>.ExcludePropertyNames = new[] { "ThemeVideoIds" };
+ ServiceStack.Text.JsConfig<Trailer>.ExcludePropertyNames = new[] { "ThemeVideoIds" };
+ ServiceStack.Text.JsConfig<BoxSet>.ExcludePropertyNames = new[] { "ThemeVideoIds" };
+ ServiceStack.Text.JsConfig<Episode>.ExcludePropertyNames = new[] { "ThemeVideoIds" };
+ ServiceStack.Text.JsConfig<Season>.ExcludePropertyNames = new[] { "ThemeVideoIds" };
+ ServiceStack.Text.JsConfig<Book>.ExcludePropertyNames = new[] { "ThemeVideoIds" };
+ ServiceStack.Text.JsConfig<CollectionFolder>.ExcludePropertyNames = new[] { "ThemeVideoIds" };
+ ServiceStack.Text.JsConfig<Folder>.ExcludePropertyNames = new[] { "ThemeVideoIds" };
+ ServiceStack.Text.JsConfig<Game>.ExcludePropertyNames = new[] { "ThemeVideoIds" };
+ ServiceStack.Text.JsConfig<GameGenre>.ExcludePropertyNames = new[] { "ThemeVideoIds" };
+ ServiceStack.Text.JsConfig<GameSystem>.ExcludePropertyNames = new[] { "ThemeVideoIds" };
+ ServiceStack.Text.JsConfig<Genre>.ExcludePropertyNames = new[] { "ThemeVideoIds" };
+ ServiceStack.Text.JsConfig<Person>.ExcludePropertyNames = new[] { "ThemeVideoIds" };
+ ServiceStack.Text.JsConfig<Photo>.ExcludePropertyNames = new[] { "ThemeVideoIds" };
+ ServiceStack.Text.JsConfig<PhotoAlbum>.ExcludePropertyNames = new[] { "ThemeVideoIds" };
+ ServiceStack.Text.JsConfig<Studio>.ExcludePropertyNames = new[] { "ThemeVideoIds" };
+ ServiceStack.Text.JsConfig<UserRootFolder>.ExcludePropertyNames = new[] { "ThemeVideoIds" };
+ ServiceStack.Text.JsConfig<UserView>.ExcludePropertyNames = new[] { "ThemeVideoIds" };
+ ServiceStack.Text.JsConfig<Video>.ExcludePropertyNames = new[] { "ThemeVideoIds" };
+ ServiceStack.Text.JsConfig<Year>.ExcludePropertyNames = new[] { "ThemeVideoIds" };
+ ServiceStack.Text.JsConfig<Channel>.ExcludePropertyNames = new[] { "ThemeVideoIds" };
+ ServiceStack.Text.JsConfig<AggregateFolder>.ExcludePropertyNames = new[] { "ThemeVideoIds" };
+
+ return result;
+ }
+
public override Task Init(IProgress<double> progress)
{
HttpPort = ServerConfigurationManager.Configuration.HttpServerPortNumber;
@@ -438,13 +632,12 @@ namespace MediaBrowser.Server.Startup.Common
RegisterSingleInstance(UserDataManager);
UserRepository = await GetUserRepository().ConfigureAwait(false);
- RegisterSingleInstance(UserRepository);
- var displayPreferencesRepo = new SqliteDisplayPreferencesRepository(LogManager, JsonSerializer, ApplicationPaths, NativeApp.GetDbConnector());
+ var displayPreferencesRepo = new SqliteDisplayPreferencesRepository(LogManager, JsonSerializer, ApplicationPaths, NativeApp.GetDbConnector(), MemoryStreamProvider);
DisplayPreferencesRepository = displayPreferencesRepo;
RegisterSingleInstance(DisplayPreferencesRepository);
- var itemRepo = new SqliteItemRepository(ServerConfigurationManager, JsonSerializer, LogManager, NativeApp.GetDbConnector());
+ var itemRepo = new SqliteItemRepository(ServerConfigurationManager, JsonSerializer, LogManager, NativeApp.GetDbConnector(), MemoryStreamProvider);
ItemRepository = itemRepo;
RegisterSingleInstance(ItemRepository);
@@ -469,17 +662,17 @@ namespace MediaBrowser.Server.Startup.Common
LibraryMonitor = new LibraryMonitor(LogManager, TaskManager, LibraryManager, ServerConfigurationManager, FileSystemManager, this);
RegisterSingleInstance(LibraryMonitor);
- ProviderManager = new ProviderManager(HttpClient, ServerConfigurationManager, LibraryMonitor, LogManager, FileSystemManager, ApplicationPaths, () => LibraryManager, JsonSerializer);
+ ProviderManager = new ProviderManager(HttpClient, ServerConfigurationManager, LibraryMonitor, LogManager, FileSystemManager, ApplicationPaths, () => LibraryManager, JsonSerializer, MemoryStreamProvider);
RegisterSingleInstance(ProviderManager);
RegisterSingleInstance<ISearchEngine>(() => new SearchEngine(LogManager, LibraryManager, UserManager));
- HttpServer = ServerFactory.CreateServer(this, LogManager, ServerConfigurationManager, NetworkManager, "Emby", "web/index.html");
+ HttpServer = ServerFactory.CreateServer(this, LogManager, ServerConfigurationManager, NetworkManager, MemoryStreamProvider, "Emby", "web/index.html");
HttpServer.GlobalResponse = LocalizationManager.GetLocalizedString("StartupEmbyServerIsLoading");
RegisterSingleInstance(HttpServer, false);
progress.Report(10);
- ServerManager = new ServerManager(this, JsonSerializer, LogManager.GetLogger("ServerManager"), ServerConfigurationManager);
+ ServerManager = new ServerManager(this, JsonSerializer, LogManager.GetLogger("ServerManager"), ServerConfigurationManager, MemoryStreamProvider);
RegisterSingleInstance(ServerManager);
var innerProgress = new ActionableProgress<double>();
@@ -491,7 +684,7 @@ namespace MediaBrowser.Server.Startup.Common
TVSeriesManager = new TVSeriesManager(UserManager, UserDataManager, LibraryManager, ServerConfigurationManager);
RegisterSingleInstance(TVSeriesManager);
- SyncManager = new SyncManager(LibraryManager, SyncRepository, ImageProcessor, LogManager.GetLogger("SyncManager"), UserManager, () => DtoService, this, TVSeriesManager, () => MediaEncoder, FileSystemManager, () => SubtitleEncoder, ServerConfigurationManager, UserDataManager, () => MediaSourceManager, JsonSerializer, TaskManager);
+ SyncManager = new SyncManager(LibraryManager, SyncRepository, ImageProcessor, LogManager.GetLogger("SyncManager"), UserManager, () => DtoService, this, TVSeriesManager, () => MediaEncoder, FileSystemManager, () => SubtitleEncoder, ServerConfigurationManager, UserDataManager, () => MediaSourceManager, JsonSerializer, TaskManager, MemoryStreamProvider);
RegisterSingleInstance(SyncManager);
DtoService = new DtoService(LogManager.GetLogger("DtoService"), LibraryManager, UserDataManager, ItemRepository, ImageProcessor, ServerConfigurationManager, FileSystemManager, ProviderManager, () => ChannelManager, SyncManager, this, () => DeviceManager, () => MediaSourceManager, () => LiveTvManager);
@@ -520,7 +713,7 @@ namespace MediaBrowser.Server.Startup.Common
MediaSourceManager = new MediaSourceManager(ItemRepository, UserManager, LibraryManager, LogManager.GetLogger("MediaSourceManager"), JsonSerializer, FileSystemManager, UserDataManager);
RegisterSingleInstance(MediaSourceManager);
- SessionManager = new SessionManager(UserDataManager, LogManager.GetLogger("SessionManager"), UserRepository, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, JsonSerializer, this, HttpClient, AuthenticationRepository, DeviceManager, MediaSourceManager);
+ SessionManager = new SessionManager(UserDataManager, LogManager.GetLogger("SessionManager"), LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, JsonSerializer, this, HttpClient, AuthenticationRepository, DeviceManager, MediaSourceManager);
RegisterSingleInstance(SessionManager);
var dlnaManager = new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LogManager.GetLogger("Dlna"), JsonSerializer, this);
@@ -553,7 +746,7 @@ namespace MediaBrowser.Server.Startup.Common
SubtitleManager = new SubtitleManager(LogManager.GetLogger("SubtitleManager"), FileSystemManager, LibraryMonitor, LibraryManager, MediaSourceManager);
RegisterSingleInstance(SubtitleManager);
- RegisterSingleInstance<IDeviceDiscovery>(new DeviceDiscovery(LogManager.GetLogger("IDeviceDiscovery"), ServerConfigurationManager, this, NetworkManager));
+ RegisterSingleInstance<IDeviceDiscovery>(new DeviceDiscovery(LogManager.GetLogger("IDeviceDiscovery"), ServerConfigurationManager));
ChapterManager = new ChapterManager(LibraryManager, LogManager.GetLogger("ChapterManager"), ServerConfigurationManager, ItemRepository);
RegisterSingleInstance(ChapterManager);
@@ -564,14 +757,10 @@ namespace MediaBrowser.Server.Startup.Common
EncodingManager = new EncodingManager(FileSystemManager, Logger, MediaEncoder, ChapterManager, LibraryManager);
RegisterSingleInstance(EncodingManager);
- RegisterSingleInstance(NativeApp.GetPowerManagement());
-
var sharingRepo = new SharingRepository(LogManager, ApplicationPaths, NativeApp.GetDbConnector());
await sharingRepo.Initialize().ConfigureAwait(false);
RegisterSingleInstance<ISharingManager>(new SharingManager(sharingRepo, ServerConfigurationManager, LibraryManager, this));
- RegisterSingleInstance<ISsdpHandler>(new SsdpHandler(LogManager.GetLogger("SsdpHandler"), ServerConfigurationManager, this));
-
var activityLogRepo = await GetActivityLogRepository().ConfigureAwait(false);
RegisterSingleInstance(activityLogRepo);
RegisterSingleInstance<IActivityManager>(new ActivityManager(LogManager.GetLogger("ActivityManager"), activityLogRepo, UserManager));
@@ -581,7 +770,7 @@ namespace MediaBrowser.Server.Startup.Common
RegisterSingleInstance<ISessionContext>(new SessionContext(UserManager, authContext, SessionManager));
RegisterSingleInstance<IAuthService>(new AuthService(UserManager, authContext, ServerConfigurationManager, ConnectManager, SessionManager, DeviceManager));
- SubtitleEncoder = new SubtitleEncoder(LibraryManager, LogManager.GetLogger("SubtitleEncoder"), ApplicationPaths, FileSystemManager, MediaEncoder, JsonSerializer, HttpClient, MediaSourceManager);
+ SubtitleEncoder = new SubtitleEncoder(LibraryManager, LogManager.GetLogger("SubtitleEncoder"), ApplicationPaths, FileSystemManager, MediaEncoder, JsonSerializer, HttpClient, MediaSourceManager, MemoryStreamProvider);
RegisterSingleInstance(SubtitleEncoder);
await displayPreferencesRepo.Initialize().ConfigureAwait(false);
@@ -673,7 +862,7 @@ namespace MediaBrowser.Server.Startup.Common
() => SubtitleEncoder,
() => MediaSourceManager,
HttpClient,
- ZipClient);
+ ZipClient, MemoryStreamProvider);
MediaEncoder = mediaEncoder;
RegisterSingleInstance(MediaEncoder);
@@ -685,19 +874,11 @@ namespace MediaBrowser.Server.Startup.Common
/// <returns>Task{IUserRepository}.</returns>
private async Task<IUserRepository> GetUserRepository()
{
- try
- {
- var repo = new SqliteUserRepository(LogManager, ApplicationPaths, JsonSerializer, NativeApp.GetDbConnector());
+ var repo = new SqliteUserRepository(LogManager, ApplicationPaths, JsonSerializer, NativeApp.GetDbConnector(), MemoryStreamProvider);
- await repo.Initialize().ConfigureAwait(false);
+ await repo.Initialize().ConfigureAwait(false);
- return repo;
- }
- catch (Exception ex)
- {
- Logger.ErrorException("Error opening user db", ex);
- throw;
- }
+ return repo;
}
/// <summary>
@@ -788,19 +969,7 @@ namespace MediaBrowser.Server.Startup.Common
/// </summary>
protected override void FindParts()
{
- var isAuthorized = ServerConfigurationManager.Configuration.IsPortAuthorized;
- if (isAuthorized)
- {
- try
- {
- isAuthorized = !NativeApp.PortsRequireAuthorization(ConfigurationManager.CommonApplicationPaths.ApplicationPath);
- }
- catch
- {
-
- }
- }
- if (!isAuthorized)
+ if (!ServerConfigurationManager.Configuration.IsPortAuthorized)
{
RegisterServerWithAdministratorAccess();
ServerConfigurationManager.Configuration.IsPortAuthorized = true;
@@ -826,7 +995,6 @@ namespace MediaBrowser.Server.Startup.Common
GetExports<IMetadataService>(),
GetExports<IMetadataProvider>(),
GetExports<IMetadataSaver>(),
- GetExports<IImageSaver>(),
GetExports<IExternalId>());
ImageProcessor.AddParts(GetExports<IImageEnhancer>());
@@ -834,7 +1002,6 @@ namespace MediaBrowser.Server.Startup.Common
LiveTvManager.AddParts(GetExports<ILiveTvService>(), GetExports<ITunerHost>(), GetExports<IListingsProvider>());
SubtitleManager.AddParts(GetExports<ISubtitleProvider>());
- ChapterManager.AddParts(GetExports<IChapterProvider>());
SessionManager.AddParts(GetExports<ISessionControllerFactory>());
@@ -858,12 +1025,12 @@ namespace MediaBrowser.Server.Startup.Common
{
var prefixes = new List<string>
{
- "http://"+i+":" + ServerConfigurationManager.Configuration.HttpServerPortNumber + "/"
+ "http://"+i+":" + HttpPort + "/"
};
if (!string.IsNullOrWhiteSpace(CertificatePath))
{
- prefixes.Add("https://" + i + ":" + ServerConfigurationManager.Configuration.HttpsPortNumber + "/");
+ prefixes.Add("https://" + i + ":" + HttpsPort + "/");
}
return prefixes;
@@ -880,6 +1047,23 @@ namespace MediaBrowser.Server.Startup.Common
try
{
ServerManager.Start(GetUrlPrefixes(), CertificatePath);
+ return;
+ }
+ catch (Exception ex)
+ {
+ Logger.ErrorException("Error starting http server", ex);
+
+ if (HttpPort == 8096)
+ {
+ throw;
+ }
+ }
+
+ HttpPort = 8096;
+
+ try
+ {
+ ServerManager.Start(GetUrlPrefixes(), CertificatePath);
}
catch (Exception ex)
{
@@ -1124,7 +1308,8 @@ namespace MediaBrowser.Server.Startup.Common
SupportsLibraryMonitor = SupportsLibraryMonitor,
EncoderLocationType = MediaEncoder.EncoderLocationType,
SystemArchitecture = NativeApp.Environment.SystemArchitecture,
- SystemUpdateLevel = ConfigurationManager.CommonConfiguration.SystemUpdateLevel
+ SystemUpdateLevel = ConfigurationManager.CommonConfiguration.SystemUpdateLevel,
+ PackageName = _startupOptions.GetOption("package")
};
}
@@ -1182,20 +1367,24 @@ namespace MediaBrowser.Server.Startup.Common
public async Task<List<IPAddress>> GetLocalIpAddresses()
{
- var localAddresses = NetworkManager.GetLocalIpAddresses()
- .Where(IsIpAddressValid)
- .ToList();
+ var addresses = NetworkManager.GetLocalIpAddresses().ToList();
+ var list = new List<IPAddress>();
- return localAddresses;
+ foreach (var address in addresses)
+ {
+ var valid = await IsIpAddressValidAsync(address).ConfigureAwait(false);
+ if (valid)
+ {
+ list.Add(address);
+ }
+ }
+
+ return list;
}
private readonly ConcurrentDictionary<string, bool> _validAddressResults = new ConcurrentDictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
private DateTime _lastAddressCacheClear;
- private bool IsIpAddressValid(IPAddress address)
- {
- return IsIpAddressValidInternal(address).Result;
- }
- private async Task<bool> IsIpAddressValidInternal(IPAddress address)
+ private async Task<bool> IsIpAddressValidAsync(IPAddress address)
{
if (IPAddress.IsLoopback(address))
{
@@ -1205,7 +1394,7 @@ namespace MediaBrowser.Server.Startup.Common
var apiUrl = GetLocalApiUrl(address);
apiUrl += "/system/ping";
- if ((DateTime.UtcNow - _lastAddressCacheClear).TotalMinutes >= 5)
+ if ((DateTime.UtcNow - _lastAddressCacheClear).TotalMinutes >= 10)
{
_lastAddressCacheClear = DateTime.UtcNow;
_validAddressResults.Clear();
@@ -1225,7 +1414,8 @@ namespace MediaBrowser.Server.Startup.Common
LogErrorResponseBody = false,
LogErrors = false,
LogRequest = false,
- TimeoutMs = 30000
+ TimeoutMs = 30000,
+ BufferContent = false
}, "POST").ConfigureAwait(false))
{
diff --git a/MediaBrowser.Server.Startup.Common/ApplicationPathHelper.cs b/MediaBrowser.Server.Startup.Common/ApplicationPathHelper.cs
index 285806791..254a782db 100644
--- a/MediaBrowser.Server.Startup.Common/ApplicationPathHelper.cs
+++ b/MediaBrowser.Server.Startup.Common/ApplicationPathHelper.cs
@@ -18,10 +18,16 @@ namespace MediaBrowser.Server.Startup.Common
useDebugPath = true;
#endif
- var programDataPath = useDebugPath ? ConfigurationManager.AppSettings["DebugProgramDataPath"] : ConfigurationManager.AppSettings["ReleaseProgramDataPath"];
+ var programDataPath = useDebugPath ?
+ ConfigurationManager.AppSettings["DebugProgramDataPath"] :
+ ConfigurationManager.AppSettings["ReleaseProgramDataPath"];
programDataPath = programDataPath.Replace("%ApplicationData%", Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData));
+ programDataPath = programDataPath
+ .Replace('/', Path.DirectorySeparatorChar)
+ .Replace('\\', Path.DirectorySeparatorChar);
+
// If it's a relative path, e.g. "..\"
if (!Path.IsPathRooted(programDataPath))
{
diff --git a/MediaBrowser.Server.Startup.Common/INativeApp.cs b/MediaBrowser.Server.Startup.Common/INativeApp.cs
index 9297a6d37..c56bb9b4b 100644
--- a/MediaBrowser.Server.Startup.Common/INativeApp.cs
+++ b/MediaBrowser.Server.Startup.Common/INativeApp.cs
@@ -2,7 +2,6 @@
using MediaBrowser.Model.Logging;
using System.Collections.Generic;
using System.Reflection;
-using MediaBrowser.Controller.Power;
using MediaBrowser.Server.Implementations.Persistence;
using MediaBrowser.Server.Startup.Common.FFMpeg;
@@ -25,8 +24,6 @@ namespace MediaBrowser.Server.Startup.Common
/// <param name="tempDirectory">The temporary directory.</param>
void AuthorizeServer(int udpPort, int httpServerPort, int httpsServerPort, string applicationPath, string tempDirectory);
- bool PortsRequireAuthorization(string applicationPath);
-
/// <summary>
/// Gets the environment.
/// </summary>
@@ -98,12 +95,6 @@ namespace MediaBrowser.Server.Startup.Common
void AllowSystemStandby();
- /// <summary>
- /// Gets the power management.
- /// </summary>
- /// <returns>IPowerManagement.</returns>
- IPowerManagement GetPowerManagement();
-
FFMpegInstallInfo GetFfmpegInstallInfo();
void LaunchUrl(string url);
diff --git a/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj b/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj
index 778002e50..3dbcaaaf4 100644
--- a/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj
+++ b/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj
@@ -9,9 +9,10 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>MediaBrowser.Server.Startup.Common</RootNamespace>
<AssemblyName>MediaBrowser.Server.Startup.Common</AssemblyName>
- <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ <TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
+ <TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@@ -47,6 +48,10 @@
<SpecificVersion>False</SpecificVersion>
<HintPath>..\ThirdParty\ServiceStack\ServiceStack.Interfaces.dll</HintPath>
</Reference>
+ <Reference Include="ServiceStack.Text, Version=4.0.0.0, Culture=neutral, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\ThirdParty\ServiceStack.Text\ServiceStack.Text.dll</HintPath>
+ </Reference>
<Reference Include="System" />
<Reference Include="System.Configuration" />
<Reference Include="System.Core" />
diff --git a/MediaBrowser.ServerApplication/MainStartup.cs b/MediaBrowser.ServerApplication/MainStartup.cs
index cdacdc81f..d1972a938 100644
--- a/MediaBrowser.ServerApplication/MainStartup.cs
+++ b/MediaBrowser.ServerApplication/MainStartup.cs
@@ -15,6 +15,7 @@ using System.Linq;
using System.Management;
using System.Runtime.InteropServices;
using System.ServiceProcess;
+using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
@@ -32,19 +33,47 @@ namespace MediaBrowser.ServerApplication
private static ILogger _logger;
private static bool _isRunningAsService = false;
+ private static bool _canRestartService = false;
private static bool _appHostDisposed;
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool SetDllDirectory(string lpPathName);
+ public static bool TryGetLocalFromUncDirectory(string local, out string unc)
+ {
+ if ((local == null) || (local == ""))
+ {
+ unc = "";
+ throw new ArgumentNullException("local");
+ }
+
+ ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT Name FROM Win32_share WHERE path ='" + local.Replace("\\", "\\\\") + "'");
+ ManagementObjectCollection coll = searcher.Get();
+ if (coll.Count == 1)
+ {
+ foreach (ManagementObject share in searcher.Get())
+ {
+ unc = share["Name"] as String;
+ unc = "\\\\" + SystemInformation.ComputerName + "\\" + unc;
+ return true;
+ }
+ }
+ unc = "";
+ return false;
+ }
/// <summary>
- /// Defines the entry point of the application.
- /// </summary>
+ /// Defines the entry point of the application.
+ /// </summary>
public static void Main()
{
var options = new StartupOptions();
_isRunningAsService = options.ContainsOption("-service");
+ if (_isRunningAsService)
+ {
+ _canRestartService = CanRestartWindowsService();
+ }
+
var currentProcess = Process.GetCurrentProcess();
var applicationPath = currentProcess.MainModule.FileName;
@@ -238,7 +267,14 @@ namespace MediaBrowser.ServerApplication
{
get
{
- return !_isRunningAsService;
+ if (_isRunningAsService)
+ {
+ return _canRestartService;
+ }
+ else
+ {
+ return true;
+ }
}
}
@@ -250,7 +286,14 @@ namespace MediaBrowser.ServerApplication
{
get
{
- return !_isRunningAsService;
+ if (_isRunningAsService)
+ {
+ return _canRestartService;
+ }
+ else
+ {
+ return true;
+ }
}
}
@@ -591,7 +634,11 @@ namespace MediaBrowser.ServerApplication
{
DisposeAppHost();
- if (!_isRunningAsService)
+ if (_isRunningAsService)
+ {
+ RestartWindowsService();
+ }
+ else
{
//_logger.Info("Hiding server notify icon");
//_serverNotifyIcon.Visible = false;
@@ -646,6 +693,47 @@ namespace MediaBrowser.ServerApplication
}
}
+ private static void RestartWindowsService()
+ {
+ _logger.Info("Restarting background service");
+
+ var startInfo = new ProcessStartInfo
+ {
+ FileName = "cmd.exe",
+ CreateNoWindow = true,
+ WindowStyle = ProcessWindowStyle.Hidden,
+ Verb = "runas",
+ ErrorDialog = false,
+ Arguments = String.Format("/c sc stop {0} & sc start {0} & sc start {0}", BackgroundService.GetExistingServiceName())
+ };
+ Process.Start(startInfo);
+ }
+
+ private static bool CanRestartWindowsService()
+ {
+ var startInfo = new ProcessStartInfo
+ {
+ FileName = "cmd.exe",
+ CreateNoWindow = true,
+ WindowStyle = ProcessWindowStyle.Hidden,
+ Verb = "runas",
+ ErrorDialog = false,
+ Arguments = String.Format("/c sc query {0}", BackgroundService.GetExistingServiceName())
+ };
+ using (var process = Process.Start(startInfo))
+ {
+ process.WaitForExit();
+ if (process.ExitCode == 0)
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ }
+
private static async Task InstallVcredist2013IfNeeded(ApplicationHost appHost, ILogger logger)
{
// Reference
diff --git a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj
index a32312493..0a7a3a6a5 100644
--- a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj
+++ b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj
@@ -83,8 +83,8 @@
<Reference Include="System.Configuration" />
<Reference Include="System.Configuration.Install" />
<Reference Include="System.Core" />
- <Reference Include="System.Data.SQLite, Version=1.0.102.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=MSIL">
- <HintPath>..\packages\System.Data.SQLite.Core.1.0.102.0\lib\net46\System.Data.SQLite.dll</HintPath>
+ <Reference Include="System.Data.SQLite, Version=1.0.103.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=MSIL">
+ <HintPath>..\packages\System.Data.SQLite.Core.1.0.103\lib\net46\System.Data.SQLite.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Drawing" />
@@ -123,7 +123,6 @@
<Compile Include="Native\Standby.cs" />
<Compile Include="Native\ServerAuthorization.cs" />
<Compile Include="Native\WindowsApp.cs" />
- <Compile Include="Native\WindowsPowerManagement.cs" />
<Compile Include="Networking\CertificateGenerator.cs" />
<Compile Include="Networking\NativeMethods.cs" />
<Compile Include="Networking\NetworkManager.cs" />
@@ -1114,12 +1113,12 @@
<PostBuildEvent>
</PostBuildEvent>
</PropertyGroup>
- <Import Project="..\packages\System.Data.SQLite.Core.1.0.102.0\build\net46\System.Data.SQLite.Core.targets" Condition="Exists('..\packages\System.Data.SQLite.Core.1.0.102.0\build\net46\System.Data.SQLite.Core.targets')" />
+ <Import Project="..\packages\System.Data.SQLite.Core.1.0.103\build\net46\System.Data.SQLite.Core.targets" Condition="Exists('..\packages\System.Data.SQLite.Core.1.0.103\build\net46\System.Data.SQLite.Core.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
- <Error Condition="!Exists('..\packages\System.Data.SQLite.Core.1.0.102.0\build\net46\System.Data.SQLite.Core.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\System.Data.SQLite.Core.1.0.102.0\build\net46\System.Data.SQLite.Core.targets'))" />
+ <Error Condition="!Exists('..\packages\System.Data.SQLite.Core.1.0.103\build\net46\System.Data.SQLite.Core.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\System.Data.SQLite.Core.1.0.103\build\net46\System.Data.SQLite.Core.targets'))" />
</Target>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
diff --git a/MediaBrowser.ServerApplication/Native/WindowsApp.cs b/MediaBrowser.ServerApplication/Native/WindowsApp.cs
index b08b82de5..7ebede40c 100644
--- a/MediaBrowser.ServerApplication/Native/WindowsApp.cs
+++ b/MediaBrowser.ServerApplication/Native/WindowsApp.cs
@@ -9,7 +9,6 @@ using System.IO;
using System.Reflection;
using System.Windows.Forms;
using CommonIO;
-using MediaBrowser.Controller.Power;
using MediaBrowser.Model.System;
using MediaBrowser.Server.Implementations.Persistence;
using MediaBrowser.Server.Startup.Common.FFMpeg;
@@ -148,11 +147,6 @@ namespace MediaBrowser.ServerApplication.Native
MainStartup.Invoke(Standby.AllowSleep);
}
- public IPowerManagement GetPowerManagement()
- {
- return new WindowsPowerManagement(_logger);
- }
-
public FFMpegInstallInfo GetFfmpegInstallInfo()
{
var info = new FFMpegInstallInfo();
@@ -210,16 +204,6 @@ namespace MediaBrowser.ServerApplication.Native
LoopUtil.Run(appName);
}
- private bool Confirm()
- {
- if (MainStartup._splash == null)
- {
- return false;
- }
-
- return MessageBox.Show(MainStartup._splash, "Emby has detected that a rule has been added to Windows Firewall that may prevent your other devices from accessing Emby Server. Click OK to remove this rule, or cancel to proceed anyway.", "Windows Firewall", MessageBoxButtons.OKCancel) == DialogResult.OK;
- }
-
public bool PortsRequireAuthorization(string applicationPath)
{
var appNameSrch = Path.GetFileName(applicationPath);
@@ -248,7 +232,6 @@ namespace MediaBrowser.ServerApplication.Native
if (data.IndexOf("Block", StringComparison.OrdinalIgnoreCase) != -1)
{
_logger.Info("Found potential windows firewall rule blocking Emby Server: " + data);
- return Confirm();
}
//var parts = data.Split('\n');
diff --git a/MediaBrowser.ServerApplication/Native/WindowsPowerManagement.cs b/MediaBrowser.ServerApplication/Native/WindowsPowerManagement.cs
deleted file mode 100644
index 866272639..000000000
--- a/MediaBrowser.ServerApplication/Native/WindowsPowerManagement.cs
+++ /dev/null
@@ -1,94 +0,0 @@
-using System;
-using System.ComponentModel;
-using System.Runtime.InteropServices;
-using System.Threading;
-using MediaBrowser.Controller.Power;
-using MediaBrowser.Model.Logging;
-using Microsoft.Win32.SafeHandles;
-
-namespace MediaBrowser.ServerApplication.Native
-{
- public class WindowsPowerManagement : IPowerManagement
- {
- [DllImport("kernel32.dll")]
- public static extern SafeWaitHandle CreateWaitableTimer(IntPtr lpTimerAttributes,
- bool bManualReset,
- string lpTimerName);
-
- [DllImport("kernel32.dll", SetLastError = true)]
- [return: MarshalAs(UnmanagedType.Bool)]
- public static extern bool SetWaitableTimer(SafeWaitHandle hTimer,
- [In] ref long pDueTime,
- int lPeriod,
- IntPtr pfnCompletionRoutine,
- IntPtr lpArgToCompletionRoutine,
- bool fResume);
-
- private BackgroundWorker _bgWorker;
- private readonly ILogger _logger;
- private readonly object _initLock = new object();
-
- public WindowsPowerManagement(ILogger logger)
- {
- _logger = logger;
- }
-
- public void ScheduleWake(DateTime utcTime)
- {
- //Initialize();
- //_bgWorker.RunWorkerAsync(utcTime.ToFileTime());
- throw new NotImplementedException();
- }
-
- private void Initialize()
- {
- lock (_initLock)
- {
- if (_bgWorker == null)
- {
- _bgWorker = new BackgroundWorker();
-
- _bgWorker.DoWork += bgWorker_DoWork;
- _bgWorker.RunWorkerCompleted += bgWorker_RunWorkerCompleted;
- }
- }
- }
-
- void bgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
- {
- //if (Woken != null)
- //{
- // Woken(this, new EventArgs());
- //}
- }
-
- private void bgWorker_DoWork(object sender, DoWorkEventArgs e)
- {
- try
- {
- long waketime = (long)e.Argument;
-
- using (SafeWaitHandle handle = CreateWaitableTimer(IntPtr.Zero, true, GetType().Assembly.GetName().Name + "Timer"))
- {
- if (SetWaitableTimer(handle, ref waketime, 0, IntPtr.Zero, IntPtr.Zero, true))
- {
- using (EventWaitHandle wh = new EventWaitHandle(false,
- EventResetMode.AutoReset))
- {
- wh.SafeWaitHandle = handle;
- wh.WaitOne();
- }
- }
- else
- {
- throw new Win32Exception(Marshal.GetLastWin32Error());
- }
- }
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error scheduling wake timer", ex);
- }
- }
- }
-}
diff --git a/MediaBrowser.ServerApplication/packages.config b/MediaBrowser.ServerApplication/packages.config
index a573c6fd9..63d3bab8a 100644
--- a/MediaBrowser.ServerApplication/packages.config
+++ b/MediaBrowser.ServerApplication/packages.config
@@ -3,5 +3,5 @@
<package id="CommonIO" version="1.0.0.9" targetFramework="net45" />
<package id="ImageMagickSharp" version="1.0.0.18" targetFramework="net45" />
<package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" />
- <package id="System.Data.SQLite.Core" version="1.0.102.0" targetFramework="net46" />
+ <package id="System.Data.SQLite.Core" version="1.0.103" targetFramework="net462" />
</packages> \ No newline at end of file
diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs
index 2f8b34879..4850b6fe0 100644
--- a/MediaBrowser.WebDashboard/Api/DashboardService.cs
+++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs
@@ -289,7 +289,7 @@ namespace MediaBrowser.WebDashboard.Api
return list;
}
- private List<Tuple<string,bool>> GetDeployIgnoreFilenames()
+ private List<Tuple<string, bool>> GetDeployIgnoreFilenames()
{
var list = new List<Tuple<string, bool>>();
@@ -313,8 +313,11 @@ namespace MediaBrowser.WebDashboard.Api
public async Task<object> Get(GetDashboardPackage request)
{
- var path = Path.Combine(_serverConfigurationManager.ApplicationPaths.ProgramDataPath,
- "webclient-dump");
+ var mode = request.Mode;
+
+ var path = string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase) ?
+ Path.Combine(_serverConfigurationManager.ApplicationPaths.ProgramDataPath, "webclient-dump")
+ : "C:\\dev\\emby-web-mobile\\src";
try
{
@@ -333,8 +336,6 @@ namespace MediaBrowser.WebDashboard.Api
var appVersion = _appHost.ApplicationVersion.ToString();
- var mode = request.Mode;
-
// Try to trim the output size a bit
var bowerPath = Path.Combine(path, "bower_components");
@@ -372,11 +373,6 @@ namespace MediaBrowser.WebDashboard.Api
// Delete things that are unneeded in an attempt to keep the output as trim as possible
_fileSystem.DeleteDirectory(Path.Combine(path, "css", "images", "tour"), true);
}
- else
- {
- MinifyCssDirectory(path);
- MinifyJsDirectory(path);
- }
await DumpHtml(creator.DashboardUIPath, path, mode, culture, appVersion);
@@ -444,78 +440,6 @@ namespace MediaBrowser.WebDashboard.Api
}
}
- private void MinifyCssDirectory(string path)
- {
- foreach (var file in Directory.GetFiles(path, "*.css", SearchOption.AllDirectories))
- {
- if (file.IndexOf(".min.", StringComparison.OrdinalIgnoreCase) != -1)
- {
- continue;
- }
- if (file.IndexOf("bower_", StringComparison.OrdinalIgnoreCase) != -1)
- {
- continue;
- }
-
- try
- {
- var text = _fileSystem.ReadAllText(file, Encoding.UTF8);
-
- var result = new KristensenCssMinifier().Minify(text, false, Encoding.UTF8);
-
- if (result.Errors.Count > 0)
- {
- Logger.Error("Error minifying css: " + result.Errors[0].Message);
- }
- else
- {
- text = result.MinifiedContent;
- _fileSystem.WriteAllText(file, text, Encoding.UTF8);
- }
- }
- catch (Exception ex)
- {
- Logger.ErrorException("Error minifying css", ex);
- }
- }
- }
-
- private void MinifyJsDirectory(string path)
- {
- foreach (var file in Directory.GetFiles(path, "*.js", SearchOption.AllDirectories))
- {
- if (file.IndexOf(".min.", StringComparison.OrdinalIgnoreCase) != -1)
- {
- continue;
- }
- if (file.IndexOf("bower_", StringComparison.OrdinalIgnoreCase) != -1)
- {
- continue;
- }
-
- try
- {
- var text = _fileSystem.ReadAllText(file, Encoding.UTF8);
-
- var result = new CrockfordJsMinifier().Minify(text, false, Encoding.UTF8);
-
- if (result.Errors.Count > 0)
- {
- Logger.Error("Error minifying javascript: " + result.Errors[0].Message);
- }
- else
- {
- text = result.MinifiedContent;
- _fileSystem.WriteAllText(file, text, Encoding.UTF8);
- }
- }
- catch (Exception ex)
- {
- Logger.ErrorException("Error minifying css", ex);
- }
- }
- }
-
private async Task DumpHtml(string source, string destination, string mode, string culture, string appVersion)
{
foreach (var file in Directory.GetFiles(source, "*", SearchOption.TopDirectoryOnly))
@@ -528,7 +452,7 @@ namespace MediaBrowser.WebDashboard.Api
private async Task DumpFile(string resourceVirtualPath, string destinationFilePath, string mode, string culture, string appVersion)
{
- using (var stream = await GetPackageCreator().GetResource(resourceVirtualPath, mode, culture, appVersion, true).ConfigureAwait(false))
+ using (var stream = await GetPackageCreator().GetResource(resourceVirtualPath, mode, culture, appVersion, false).ConfigureAwait(false))
{
using (var fs = _fileSystem.GetFileStream(destinationFilePath, FileMode.Create, FileAccess.Write, FileShare.Read))
{
diff --git a/MediaBrowser.WebDashboard/Api/PackageCreator.cs b/MediaBrowser.WebDashboard/Api/PackageCreator.cs
index e7247a11f..eaac3e2a0 100644
--- a/MediaBrowser.WebDashboard/Api/PackageCreator.cs
+++ b/MediaBrowser.WebDashboard/Api/PackageCreator.cs
@@ -346,7 +346,7 @@ namespace MediaBrowser.WebDashboard.Api
if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase))
{
- sb.Append("<meta http-equiv=\"Content-Security-Policy\" content=\"default-src * 'self' 'unsafe-inline' 'unsafe-eval' data: gap: file: filesystem:; connect-src ws: *\">");
+ sb.Append("<meta http-equiv=\"Content-Security-Policy\" content=\"default-src * 'self' 'unsafe-inline' 'unsafe-eval' data: gap: file: filesystem: ws: wss:;\">");
}
else
{
diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
index 2b828b8fc..b57416fab 100644
--- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
+++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
@@ -104,6 +104,12 @@
<Content Include="dashboard-ui\camerauploadsettings.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\components\accessschedule\accessschedule.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\components\accessschedule\accessschedule.template.html">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\components\appfooter\appfooter.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -437,18 +443,6 @@
<Content Include="dashboard-ui\scripts\sections.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.checkbox.css">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.collapsible.css">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.collapsible.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.controlgroup.css">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.listview.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -470,18 +464,9 @@
<Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.slider.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.checkbox.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.widget.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\jquery.mobile.custom.icons.css">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\jquery.mobile.custom.theme.css">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\thirdparty\paper-button-style.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -983,9 +968,6 @@
<Content Include="dashboard-ui\scripts\nowplayingpage.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\scripts\ratingdialog.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\scripts\episodes.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
diff --git a/MediaBrowser.XbmcMetadata/EntryPoint.cs b/MediaBrowser.XbmcMetadata/EntryPoint.cs
index 0844f1f74..bf3d3c303 100644
--- a/MediaBrowser.XbmcMetadata/EntryPoint.cs
+++ b/MediaBrowser.XbmcMetadata/EntryPoint.cs
@@ -91,6 +91,16 @@ namespace MediaBrowser.XbmcMetadata
return;
}
+ if (!item.SupportsLocalMetadata)
+ {
+ return;
+ }
+
+ if (!item.IsSaveLocalMetadataEnabled())
+ {
+ return;
+ }
+
try
{
await _providerManager.SaveMetadata(item, updateReason, new[] { BaseNfoSaver.SaverName }).ConfigureAwait(false);
diff --git a/MediaBrowser.XbmcMetadata/Images/XbmcImageSaver.cs b/MediaBrowser.XbmcMetadata/Images/XbmcImageSaver.cs
deleted file mode 100644
index 01eeb5f3b..000000000
--- a/MediaBrowser.XbmcMetadata/Images/XbmcImageSaver.cs
+++ /dev/null
@@ -1,272 +0,0 @@
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Audio;
-using MediaBrowser.Controller.Entities.TV;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Drawing;
-using MediaBrowser.Model.Entities;
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-
-namespace MediaBrowser.XbmcMetadata.Images
-{
- public class XbmcImageSaver : IImageFileSaver
- {
- private readonly CultureInfo _usCulture = new CultureInfo("en-US");
-
- public IEnumerable<string> GetSavePaths(IHasImages item, ImageType type, ImageFormat format, int index)
- {
- var season = item as Season;
-
- if (!SupportsItem(item, type, season))
- {
- return new string[] { };
- }
-
- var extension = "." + format.ToString().ToLower();
-
- // Backdrop paths
- if (type == ImageType.Backdrop)
- {
- if (index == 0)
- {
- if (item.IsInMixedFolder)
- {
- return new[] { GetSavePathForItemInMixedFolder(item, type, "fanart", extension) };
- }
-
- if (season != null && season.IndexNumber.HasValue)
- {
- var seriesFolder = season.SeriesPath;
-
- var seasonMarker = season.IndexNumber.Value == 0
- ? "-specials"
- : season.IndexNumber.Value.ToString("00", _usCulture);
-
- var imageFilename = "season" + seasonMarker + "-fanart" + extension;
-
- return new[] { Path.Combine(seriesFolder, imageFilename) };
- }
-
- return new[]
- {
- Path.Combine(item.ContainingFolderPath, "fanart" + extension)
- };
- }
-
- if (item.IsInMixedFolder)
- {
- return new[] { GetSavePathForItemInMixedFolder(item, type, "fanart" + index.ToString(_usCulture), extension) };
- }
-
- var extraFanartFilename = GetBackdropSaveFilename(item.GetImages(ImageType.Backdrop), "fanart", "fanart", index);
-
- return new[]
- {
- Path.Combine(item.ContainingFolderPath, "extrafanart", extraFanartFilename + extension),
- Path.Combine(item.ContainingFolderPath, "extrathumbs", "thumb" + index.ToString(_usCulture) + extension)
- };
- }
-
- if (type == ImageType.Primary)
- {
- if (season != null && season.IndexNumber.HasValue)
- {
- var seriesFolder = season.SeriesPath;
-
- var seasonMarker = season.IndexNumber.Value == 0
- ? "-specials"
- : season.IndexNumber.Value.ToString("00", _usCulture);
-
- var imageFilename = "season" + seasonMarker + "-poster" + extension;
-
- return new[] { Path.Combine(seriesFolder, imageFilename) };
- }
-
- if (item is Episode)
- {
- var seasonFolder = Path.GetDirectoryName(item.Path);
-
- var imageFilename = Path.GetFileNameWithoutExtension(item.Path) + "-thumb" + extension;
-
- return new[] { Path.Combine(seasonFolder, imageFilename) };
- }
-
- if (item.IsInMixedFolder || item is MusicVideo)
- {
- return new[] { GetSavePathForItemInMixedFolder(item, type, string.Empty, extension) };
- }
-
- if (item is MusicAlbum || item is MusicArtist)
- {
- return new[] { Path.Combine(item.ContainingFolderPath, "folder" + extension) };
- }
-
- return new[] { Path.Combine(item.ContainingFolderPath, "poster" + extension) };
- }
-
- if (type == ImageType.Banner)
- {
- if (season != null && season.IndexNumber.HasValue)
- {
- var seriesFolder = season.SeriesPath;
-
- var seasonMarker = season.IndexNumber.Value == 0
- ? "-specials"
- : season.IndexNumber.Value.ToString("00", _usCulture);
-
- var imageFilename = "season" + seasonMarker + "-banner" + extension;
-
- return new[] { Path.Combine(seriesFolder, imageFilename) };
- }
- }
-
- if (type == ImageType.Thumb)
- {
- if (season != null && season.IndexNumber.HasValue)
- {
- var seriesFolder = season.SeriesPath;
-
- var seasonMarker = season.IndexNumber.Value == 0
- ? "-specials"
- : season.IndexNumber.Value.ToString("00", _usCulture);
-
- var imageFilename = "season" + seasonMarker + "-landscape" + extension;
-
- return new[] { Path.Combine(seriesFolder, imageFilename) };
- }
-
- if (item.IsInMixedFolder)
- {
- return new[] { GetSavePathForItemInMixedFolder(item, type, "landscape", extension) };
- }
-
- return new[] { Path.Combine(item.ContainingFolderPath, "landscape" + extension) };
- }
-
- return GetStandardSavePaths(item, type, index, extension);
- }
-
- private IEnumerable<string> GetStandardSavePaths(IHasImages item, ImageType type, int imageIndex, string extension)
- {
- string filename;
-
- switch (type)
- {
- case ImageType.Art:
- filename = "clearart";
- break;
- case ImageType.BoxRear:
- filename = "back";
- break;
- case ImageType.Disc:
- filename = item is MusicAlbum ? "cdart" : "disc";
- break;
- case ImageType.Screenshot:
- filename = GetBackdropSaveFilename(item.GetImages(type), "screenshot", "screenshot", imageIndex);
- break;
- default:
- filename = type.ToString().ToLower();
- break;
- }
-
- string path = null;
-
- if (item.IsInMixedFolder)
- {
- path = GetSavePathForItemInMixedFolder(item, type, filename, extension);
- }
-
- if (string.IsNullOrEmpty(path))
- {
- path = Path.Combine(item.ContainingFolderPath, filename + extension);
- }
-
- if (string.IsNullOrEmpty(path))
- {
- return new string[] { };
- }
-
- return new[] { path };
- }
-
-
- private string GetSavePathForItemInMixedFolder(IHasImages item, ImageType type, string imageFilename, string extension)
- {
- if (type == ImageType.Primary)
- {
- imageFilename = "poster";
- }
- var folder = Path.GetDirectoryName(item.Path);
-
- return Path.Combine(folder, Path.GetFileNameWithoutExtension(item.Path) + "-" + imageFilename + extension);
- }
-
- private bool SupportsItem(IHasImages item, ImageType type, Season season)
- {
- if (item.IsOwnedItem || item is Audio || item is User)
- {
- return false;
- }
-
- if (type != ImageType.Primary && item is Episode)
- {
- return false;
- }
-
- if (!item.SupportsLocalMetadata)
- {
- return false;
- }
-
- var locationType = item.LocationType;
- if (locationType == LocationType.Remote || locationType == LocationType.Virtual)
- {
- var allowSaving = false;
-
- // If season is virtual under a physical series, save locally if using compatible convention
- if (season != null)
- {
- var series = season.Series;
-
- if (series != null && series.SupportsLocalMetadata)
- {
- allowSaving = true;
- }
- }
-
- if (!allowSaving)
- {
- return false;
- }
- }
-
- return true;
- }
-
- private string GetBackdropSaveFilename(IEnumerable<ItemImageInfo> images, string zeroIndexFilename, string numberedIndexPrefix, int? index)
- {
- if (index.HasValue && index.Value == 0)
- {
- return zeroIndexFilename;
- }
-
- var filenames = images.Select(i => Path.GetFileNameWithoutExtension(i.Path)).ToList();
-
- var current = 1;
- while (filenames.Contains(numberedIndexPrefix + current.ToString(_usCulture), StringComparer.OrdinalIgnoreCase))
- {
- current++;
- }
-
- return numberedIndexPrefix + current.ToString(_usCulture);
- }
-
- public string Name
- {
- get { return "Emby/Plex/Xbmc Images"; }
- }
- }
-}
diff --git a/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj b/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj
index d95d8f12d..f3147a065 100644
--- a/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj
+++ b/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj
@@ -52,7 +52,6 @@
</Compile>
<Compile Include="Configuration\NfoOptions.cs" />
<Compile Include="EntryPoint.cs" />
- <Compile Include="Images\XbmcImageSaver.cs" />
<Compile Include="Parsers\BaseNfoParser.cs" />
<Compile Include="Parsers\EpisodeNfoParser.cs" />
<Compile Include="Parsers\MovieNfoParser.cs" />
diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
index 1f222e75c..8c45b8001 100644
--- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
+++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
@@ -492,13 +492,9 @@ namespace MediaBrowser.XbmcMetadata.Parsers
{
var val = reader.ReadElementContentAsString();
- var hasTagline = item as IHasTaglines;
- if (hasTagline != null)
+ if (!string.IsNullOrWhiteSpace(val))
{
- if (!string.IsNullOrWhiteSpace(val))
- {
- hasTagline.AddTagline(val);
- }
+ item.Tagline = val;
}
break;
}
@@ -507,20 +503,12 @@ namespace MediaBrowser.XbmcMetadata.Parsers
{
var val = reader.ReadElementContentAsString();
- var hasProductionLocations = item as IHasProductionLocations;
- if (hasProductionLocations != null)
+ if (!string.IsNullOrWhiteSpace(val))
{
- if (!string.IsNullOrWhiteSpace(val))
- {
- var parts = val.Split('/')
- .Select(i => i.Trim())
- .Where(i => !string.IsNullOrWhiteSpace(i));
-
- foreach (var p in parts)
- {
- hasProductionLocations.AddProductionLocation(p);
- }
- }
+ item.ProductionLocations = val.Split('/')
+ .Select(i => i.Trim())
+ .Where(i => !string.IsNullOrWhiteSpace(i))
+ .ToList();
}
break;
}
diff --git a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs
index 290ea588e..0b071fceb 100644
--- a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs
+++ b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs
@@ -718,22 +718,14 @@ namespace MediaBrowser.XbmcMetadata.Savers
writer.WriteElementString("runtime", Convert.ToInt32(timespan.TotalMinutes).ToString(UsCulture));
}
- var hasTaglines = item as IHasTaglines;
- if (hasTaglines != null)
+ if (!string.IsNullOrWhiteSpace(item.Tagline))
{
- foreach (var tagline in hasTaglines.Taglines)
- {
- writer.WriteElementString("tagline", tagline);
- }
+ writer.WriteElementString("tagline", item.Tagline);
}
- var hasProductionLocations = item as IHasProductionLocations;
- if (hasProductionLocations != null)
+ foreach (var country in item.ProductionLocations)
{
- foreach (var country in hasProductionLocations.ProductionLocations)
- {
- writer.WriteElementString("country", country);
- }
+ writer.WriteElementString("country", country);
}
foreach (var genre in item.Genres)
@@ -1040,12 +1032,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
private static string GetPathToSave(string path, ILibraryManager libraryManager, IServerConfigurationManager config)
{
- foreach (var map in config.Configuration.PathSubstitutions)
- {
- path = libraryManager.SubstitutePath(path, map.From, map.To);
- }
-
- return path;
+ return libraryManager.GetPathAfterNetworkSubstitution(path);
}
private static bool IsPersonType(PersonInfo person, string type)
diff --git a/MediaBrowser.sln b/MediaBrowser.sln
index 0b9dd90cd..7e0d834fa 100644
--- a/MediaBrowser.sln
+++ b/MediaBrowser.sln
@@ -64,6 +64,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Server.Startup
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Emby.Drawing", "Emby.Drawing\Emby.Drawing.csproj", "{08FFF49B-F175-4807-A2B5-73B0EBD9F716}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mono.Nat", "Mono.Nat\Mono.Nat.csproj", "{D7453B88-2266-4805-B39B-2B5A2A33E1BA}"
+EndProject
Global
GlobalSection(Performance) = preSolution
HasPerformanceSessions = true
@@ -522,6 +524,36 @@ Global
{08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|Win32.ActiveCfg = Release|Any CPU
{08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|x64.ActiveCfg = Release|Any CPU
{08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|x86.ActiveCfg = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|Win32.ActiveCfg = Debug|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|Win32.Build.0 = Debug|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|x64.Build.0 = Debug|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|x86.Build.0 = Debug|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|Any CPU.ActiveCfg = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|Any CPU.Build.0 = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|Mixed Platforms.Build.0 = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|Win32.ActiveCfg = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|Win32.Build.0 = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|x64.ActiveCfg = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|x64.Build.0 = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|x86.ActiveCfg = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|x86.Build.0 = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|Win32.ActiveCfg = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|Win32.Build.0 = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|x64.ActiveCfg = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|x64.Build.0 = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|x86.ActiveCfg = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/Mono.Nat/AbstractNatDevice.cs b/Mono.Nat/AbstractNatDevice.cs
new file mode 100644
index 000000000..046cfc10f
--- /dev/null
+++ b/Mono.Nat/AbstractNatDevice.cs
@@ -0,0 +1,97 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+// Ben Motmans <ben.motmans@gmail.com>
+//
+// Copyright (C) 2006 Alan McGovern
+// Copyright (C) 2007 Ben Motmans
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Net;
+
+namespace Mono.Nat
+{
+ public abstract class AbstractNatDevice : INatDevice
+ {
+ private DateTime lastSeen;
+
+ protected AbstractNatDevice ()
+ {
+
+ }
+
+ public abstract IPAddress LocalAddress { get; }
+
+ public DateTime LastSeen
+ {
+ get { return lastSeen; }
+ set { lastSeen = value; }
+ }
+
+ public virtual void CreatePortMap (Mapping mapping)
+ {
+ IAsyncResult result = BeginCreatePortMap (mapping, null, null);
+ EndCreatePortMap(result);
+ }
+
+ public virtual void DeletePortMap (Mapping mapping)
+ {
+ IAsyncResult result = BeginDeletePortMap (mapping, null, mapping);
+ EndDeletePortMap(result);
+ }
+
+ public virtual Mapping[] GetAllMappings ()
+ {
+ IAsyncResult result = BeginGetAllMappings (null, null);
+ return EndGetAllMappings (result);
+ }
+
+ public virtual IPAddress GetExternalIP ()
+ {
+ IAsyncResult result = BeginGetExternalIP(null, null);
+ return EndGetExternalIP(result);
+ }
+
+ public virtual Mapping GetSpecificMapping (Protocol protocol, int port)
+ {
+ IAsyncResult result = this.BeginGetSpecificMapping (protocol, port, null, null);
+ return this.EndGetSpecificMapping(result);
+ }
+
+ public abstract IAsyncResult BeginCreatePortMap(Mapping mapping, AsyncCallback callback, object asyncState);
+ public abstract IAsyncResult BeginDeletePortMap (Mapping mapping, AsyncCallback callback, object asyncState);
+
+ public abstract IAsyncResult BeginGetAllMappings (AsyncCallback callback, object asyncState);
+ public abstract IAsyncResult BeginGetExternalIP (AsyncCallback callback, object asyncState);
+ public abstract IAsyncResult BeginGetSpecificMapping(Protocol protocol, int externalPort, AsyncCallback callback, object asyncState);
+
+ public abstract void EndCreatePortMap (IAsyncResult result);
+ public abstract void EndDeletePortMap (IAsyncResult result);
+
+ public abstract Mapping[] EndGetAllMappings (IAsyncResult result);
+ public abstract IPAddress EndGetExternalIP (IAsyncResult result);
+ public abstract Mapping EndGetSpecificMapping (IAsyncResult result);
+ }
+}
diff --git a/Mono.Nat/AsyncResults/AsyncResult.cs b/Mono.Nat/AsyncResults/AsyncResult.cs
new file mode 100644
index 000000000..e98e7d7ca
--- /dev/null
+++ b/Mono.Nat/AsyncResults/AsyncResult.cs
@@ -0,0 +1,71 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading;
+
+namespace Mono.Nat
+{
+ internal class AsyncResult : IAsyncResult
+ {
+ private object asyncState;
+ private AsyncCallback callback;
+ private bool completedSynchronously;
+ private bool isCompleted;
+ private Exception storedException;
+ private ManualResetEvent waitHandle;
+
+ public AsyncResult(AsyncCallback callback, object asyncState)
+ {
+ this.callback = callback;
+ this.asyncState = asyncState;
+ waitHandle = new ManualResetEvent(false);
+ }
+
+ public object AsyncState
+ {
+ get { return asyncState; }
+ }
+
+ public ManualResetEvent AsyncWaitHandle
+ {
+ get { return waitHandle; }
+ }
+
+ WaitHandle IAsyncResult.AsyncWaitHandle
+ {
+ get { return waitHandle; }
+ }
+
+ public bool CompletedSynchronously
+ {
+ get { return completedSynchronously; }
+ protected internal set { completedSynchronously = value; }
+ }
+
+ public bool IsCompleted
+ {
+ get { return isCompleted; }
+ protected internal set { isCompleted = value; }
+ }
+
+ public Exception StoredException
+ {
+ get { return storedException; }
+ }
+
+ public void Complete()
+ {
+ Complete(storedException);
+ }
+
+ public void Complete(Exception ex)
+ {
+ storedException = ex;
+ isCompleted = true;
+ waitHandle.Set();
+
+ if (callback != null)
+ callback(this);
+ }
+ }
+}
diff --git a/Mono.Nat/Enums/MapState.cs b/Mono.Nat/Enums/MapState.cs
new file mode 100644
index 000000000..5ed2abd8f
--- /dev/null
+++ b/Mono.Nat/Enums/MapState.cs
@@ -0,0 +1,36 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace Mono.Nat
+{
+ public enum MapState
+ {
+ AlreadyMapped,
+ Available
+ }
+} \ No newline at end of file
diff --git a/Mono.Nat/Enums/ProtocolType.cs b/Mono.Nat/Enums/ProtocolType.cs
new file mode 100644
index 000000000..a1f5cbb0e
--- /dev/null
+++ b/Mono.Nat/Enums/ProtocolType.cs
@@ -0,0 +1,36 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace Mono.Nat
+{
+ public enum Protocol
+ {
+ Tcp,
+ Udp
+ }
+} \ No newline at end of file
diff --git a/Mono.Nat/EventArgs/DeviceEventArgs.cs b/Mono.Nat/EventArgs/DeviceEventArgs.cs
new file mode 100644
index 000000000..fbbbf63e3
--- /dev/null
+++ b/Mono.Nat/EventArgs/DeviceEventArgs.cs
@@ -0,0 +1,45 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace Mono.Nat
+{
+ public class DeviceEventArgs : EventArgs
+ {
+ private INatDevice device;
+
+ public DeviceEventArgs(INatDevice device)
+ {
+ this.device = device;
+ }
+
+ public INatDevice Device
+ {
+ get { return this.device; }
+ }
+ }
+} \ No newline at end of file
diff --git a/Mono.Nat/Exceptions/MappingException.cs b/Mono.Nat/Exceptions/MappingException.cs
new file mode 100644
index 000000000..bb2e6a69d
--- /dev/null
+++ b/Mono.Nat/Exceptions/MappingException.cs
@@ -0,0 +1,87 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Security.Permissions;
+
+namespace Mono.Nat
+{
+ [Serializable]
+ public class MappingException : Exception
+ {
+ private int errorCode;
+ private string errorText;
+
+ public int ErrorCode
+ {
+ get { return this.errorCode; }
+ }
+
+ public string ErrorText
+ {
+ get { return this.errorText; }
+ }
+
+ #region Constructors
+ public MappingException()
+ : base()
+ {
+ }
+
+ public MappingException(string message)
+ : base(message)
+ {
+ }
+
+ public MappingException(int errorCode, string errorText)
+ : base (string.Format ("Error {0}: {1}", errorCode, errorText))
+ {
+ this.errorCode = errorCode;
+ this.errorText = errorText;
+ }
+
+ public MappingException(string message, Exception innerException)
+ : base(message, innerException)
+ {
+ }
+
+ protected MappingException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
+ : base(info, context)
+ {
+ }
+ #endregion
+
+ [SecurityPermission(SecurityAction.Demand, SerializationFormatter=true)]
+ public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
+ {
+ if(info==null) throw new ArgumentNullException("info");
+
+ this.errorCode = info.GetInt32("errorCode");
+ this.errorText = info.GetString("errorText");
+ base.GetObjectData(info, context);
+ }
+ }
+}
diff --git a/Mono.Nat/IMapper.cs b/Mono.Nat/IMapper.cs
new file mode 100644
index 000000000..b18e6cff2
--- /dev/null
+++ b/Mono.Nat/IMapper.cs
@@ -0,0 +1,50 @@
+//
+// Authors:
+// Nicholas Terry <nick.i.terry@gmail.com>
+//
+// Copyright (C) 2014 Nicholas Terry
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+
+namespace Mono.Nat
+{
+ public enum MapperType
+ {
+ Pmp,
+ Upnp
+ }
+
+ internal interface IMapper
+ {
+ event EventHandler<DeviceEventArgs> DeviceFound;
+
+ void Map(IPAddress gatewayAddress);
+
+ void Handle(IPAddress localAddres, byte[] response);
+ }
+}
diff --git a/Mono.Nat/INatDevice.cs b/Mono.Nat/INatDevice.cs
new file mode 100644
index 000000000..c9f27055b
--- /dev/null
+++ b/Mono.Nat/INatDevice.cs
@@ -0,0 +1,62 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+// Ben Motmans <ben.motmans@gmail.com>
+//
+// Copyright (C) 2006 Alan McGovern
+// Copyright (C) 2007 Ben Motmans
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Net;
+
+namespace Mono.Nat
+{
+ public interface INatDevice
+ {
+ void CreatePortMap (Mapping mapping);
+ void DeletePortMap (Mapping mapping);
+
+ IPAddress LocalAddress { get; }
+ Mapping[] GetAllMappings ();
+ IPAddress GetExternalIP ();
+ Mapping GetSpecificMapping (Protocol protocol, int port);
+
+ IAsyncResult BeginCreatePortMap (Mapping mapping, AsyncCallback callback, object asyncState);
+ IAsyncResult BeginDeletePortMap (Mapping mapping, AsyncCallback callback, object asyncState);
+
+ IAsyncResult BeginGetAllMappings (AsyncCallback callback, object asyncState);
+ IAsyncResult BeginGetExternalIP (AsyncCallback callback, object asyncState);
+ IAsyncResult BeginGetSpecificMapping (Protocol protocol, int externalPort, AsyncCallback callback, object asyncState);
+
+ void EndCreatePortMap (IAsyncResult result);
+ void EndDeletePortMap (IAsyncResult result);
+
+ Mapping[] EndGetAllMappings (IAsyncResult result);
+ IPAddress EndGetExternalIP (IAsyncResult result);
+ Mapping EndGetSpecificMapping (IAsyncResult result);
+
+ DateTime LastSeen { get; set; }
+ }
+}
diff --git a/Mono.Nat/ISearcher.cs b/Mono.Nat/ISearcher.cs
new file mode 100644
index 000000000..56e438105
--- /dev/null
+++ b/Mono.Nat/ISearcher.cs
@@ -0,0 +1,51 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+// Ben Motmans <ben.motmans@gmail.com>
+// Nicholas Terry <nick.i.terry@gmail.com>
+//
+// Copyright (C) 2006 Alan McGovern
+// Copyright (C) 2007 Ben Motmans
+// Copyright (C) 2014 Nicholas Terry
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Net.Sockets;
+using System.Net;
+
+namespace Mono.Nat
+{
+ public delegate void NatDeviceCallback(INatDevice device);
+
+ internal interface ISearcher
+ {
+ event EventHandler<DeviceEventArgs> DeviceFound;
+ event EventHandler<DeviceEventArgs> DeviceLost;
+
+ void Search();
+ void Handle(IPAddress localAddress, byte[] response, IPEndPoint endpoint);
+ DateTime NextSearch { get; }
+ NatProtocol Protocol { get; }
+ }
+}
diff --git a/Mono.Nat/Mapping.cs b/Mono.Nat/Mapping.cs
new file mode 100644
index 000000000..dd49404c6
--- /dev/null
+++ b/Mono.Nat/Mapping.cs
@@ -0,0 +1,123 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+// Ben Motmans <ben.motmans@gmail.com>
+//
+// Copyright (C) 2006 Alan McGovern
+// Copyright (C) 2007 Ben Motmans
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace Mono.Nat
+{
+ public class Mapping
+ {
+ private string description;
+ private DateTime expiration;
+ private int lifetime;
+ private int privatePort;
+ private Protocol protocol;
+ private int publicPort;
+
+
+
+ public Mapping (Protocol protocol, int privatePort, int publicPort)
+ : this (protocol, privatePort, publicPort, 0)
+ {
+ }
+
+ public Mapping (Protocol protocol, int privatePort, int publicPort, int lifetime)
+ {
+ this.protocol = protocol;
+ this.privatePort = privatePort;
+ this.publicPort = publicPort;
+ this.lifetime = lifetime;
+
+ if (lifetime == int.MaxValue)
+ this.expiration = DateTime.MaxValue;
+ else if (lifetime == 0)
+ this.expiration = DateTime.Now;
+ else
+ this.expiration = DateTime.Now.AddSeconds (lifetime);
+ }
+
+ public string Description
+ {
+ get { return description; }
+ set { description = value; }
+ }
+
+ public Protocol Protocol
+ {
+ get { return protocol; }
+ internal set { protocol = value; }
+ }
+
+ public int PrivatePort
+ {
+ get { return privatePort; }
+ internal set { privatePort = value; }
+ }
+
+ public int PublicPort
+ {
+ get { return publicPort; }
+ internal set { publicPort = value; }
+ }
+
+ public int Lifetime
+ {
+ get { return lifetime; }
+ internal set { lifetime = value; }
+ }
+
+ public DateTime Expiration
+ {
+ get { return expiration; }
+ internal set { expiration = value; }
+ }
+
+ public bool IsExpired ()
+ {
+ return expiration < DateTime.Now;
+ }
+
+ public override bool Equals (object obj)
+ {
+ Mapping other = obj as Mapping;
+ return other == null ? false : this.protocol == other.protocol &&
+ this.privatePort == other.privatePort && this.publicPort == other.publicPort;
+ }
+
+ public override int GetHashCode()
+ {
+ return this.protocol.GetHashCode() ^ this.privatePort.GetHashCode() ^ this.publicPort.GetHashCode();
+ }
+
+ public override string ToString( )
+ {
+ return String.Format( "Protocol: {0}, Public Port: {1}, Private Port: {2}, Description: {3}, Expiration: {4}, Lifetime: {5}",
+ this.protocol, this.publicPort, this.privatePort, this.description, this.expiration, this.lifetime );
+ }
+ }
+}
diff --git a/Mono.Nat/Mono.Nat.csproj b/Mono.Nat/Mono.Nat.csproj
new file mode 100644
index 000000000..9c2781433
--- /dev/null
+++ b/Mono.Nat/Mono.Nat.csproj
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProjectGuid>{D7453B88-2266-4805-B39B-2B5A2A33E1BA}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>Mono.Nat</RootNamespace>
+ <AssemblyName>Mono.Nat</AssemblyName>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ <TargetFrameworkProfile />
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="System" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Xml.Linq" />
+ <Reference Include="System.Data.DataSetExtensions" />
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Net.Http" />
+ <Reference Include="System.Xml" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="..\SharedVersion.cs">
+ <Link>Properties\SharedVersion.cs</Link>
+ </Compile>
+ <Compile Include="AbstractNatDevice.cs" />
+ <Compile Include="AsyncResults\AsyncResult.cs" />
+ <Compile Include="Enums\MapState.cs" />
+ <Compile Include="Enums\ProtocolType.cs" />
+ <Compile Include="EventArgs\DeviceEventArgs.cs" />
+ <Compile Include="Exceptions\MappingException.cs" />
+ <Compile Include="IMapper.cs" />
+ <Compile Include="INatDevice.cs" />
+ <Compile Include="ISearcher.cs" />
+ <Compile Include="Mapping.cs" />
+ <Compile Include="NatProtocol.cs" />
+ <Compile Include="NatUtility.cs" />
+ <Compile Include="Pmp\AsyncResults\PortMapAsyncResult.cs" />
+ <Compile Include="Pmp\Mappers\PmpMapper.cs" />
+ <Compile Include="Pmp\Pmp.cs" />
+ <Compile Include="Pmp\PmpConstants.cs" />
+ <Compile Include="Pmp\PmpNatDevice.cs" />
+ <Compile Include="Pmp\Searchers\PmpSearcher.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="Upnp\AsyncResults\GetAllMappingsAsyncResult.cs" />
+ <Compile Include="Upnp\AsyncResults\PortMapAsyncResult.cs" />
+ <Compile Include="Upnp\Mappers\UpnpMapper.cs" />
+ <Compile Include="Upnp\Messages\DiscoverDeviceMessage.cs" />
+ <Compile Include="Upnp\Messages\ErrorMessage.cs" />
+ <Compile Include="Upnp\Messages\GetServicesMessage.cs" />
+ <Compile Include="Upnp\Messages\Requests\CreatePortMappingMessage.cs" />
+ <Compile Include="Upnp\Messages\Requests\DeletePortMappingMessage.cs" />
+ <Compile Include="Upnp\Messages\Requests\GetExternalIPAddressMessage.cs" />
+ <Compile Include="Upnp\Messages\Requests\GetGenericPortMappingEntry.cs" />
+ <Compile Include="Upnp\Messages\Requests\GetSpecificPortMappingEntryMessage.cs" />
+ <Compile Include="Upnp\Messages\Responses\CreatePortMappingResponseMessage.cs" />
+ <Compile Include="Upnp\Messages\Responses\DeletePortMappingResponseMessage.cs" />
+ <Compile Include="Upnp\Messages\Responses\GetExternalIPAddressResponseMessage.cs" />
+ <Compile Include="Upnp\Messages\Responses\GetGenericPortMappingEntryResponseMessage.cs" />
+ <Compile Include="Upnp\Messages\UpnpMessage.cs" />
+ <Compile Include="Upnp\Searchers\UpnpSearcher.cs" />
+ <Compile Include="Upnp\Upnp.cs" />
+ <Compile Include="Upnp\UpnpNatDevice.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj">
+ <Project>{17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2}</Project>
+ <Name>MediaBrowser.Controller</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj">
+ <Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project>
+ <Name>MediaBrowser.Model</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+ <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
+ Other similar extension points exist, see Microsoft.Common.targets.
+ <Target Name="BeforeBuild">
+ </Target>
+ <Target Name="AfterBuild">
+ </Target>
+ -->
+</Project> \ No newline at end of file
diff --git a/Mono.Nat/NatProtocol.cs b/Mono.Nat/NatProtocol.cs
new file mode 100644
index 000000000..ade8d921c
--- /dev/null
+++ b/Mono.Nat/NatProtocol.cs
@@ -0,0 +1,9 @@
+
+namespace Mono.Nat
+{
+ public enum NatProtocol
+ {
+ Upnp = 0,
+ Pmp = 1
+ }
+}
diff --git a/Mono.Nat/NatUtility.cs b/Mono.Nat/NatUtility.cs
new file mode 100644
index 000000000..6d91d2513
--- /dev/null
+++ b/Mono.Nat/NatUtility.cs
@@ -0,0 +1,264 @@
+//
+// Authors:
+// Ben Motmans <ben.motmans@gmail.com>
+// Nicholas Terry <nick.i.terry@gmail.com>
+//
+// Copyright (C) 2007 Ben Motmans
+// Copyright (C) 2014 Nicholas Terry
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading;
+using System.Linq;
+using System.Collections.Generic;
+using System.IO;
+using System.Net.NetworkInformation;
+using MediaBrowser.Controller.Dlna;
+using MediaBrowser.Model.Logging;
+using Mono.Nat.Pmp.Mappers;
+using Mono.Nat.Upnp.Mappers;
+
+namespace Mono.Nat
+{
+ public static class NatUtility
+ {
+ private static ManualResetEvent searching;
+ public static event EventHandler<DeviceEventArgs> DeviceFound;
+ public static event EventHandler<DeviceEventArgs> DeviceLost;
+
+ public static event EventHandler<UnhandledExceptionEventArgs> UnhandledException;
+
+ private static List<ISearcher> controllers;
+ private static bool verbose;
+
+ public static List<NatProtocol> EnabledProtocols { get; set; }
+
+ public static ILogger Logger { get; set; }
+
+ public static bool Verbose
+ {
+ get { return verbose; }
+ set { verbose = value; }
+ }
+
+ static NatUtility()
+ {
+ EnabledProtocols = new List<NatProtocol>
+ {
+ NatProtocol.Upnp,
+ NatProtocol.Pmp
+ };
+
+ searching = new ManualResetEvent(false);
+
+ controllers = new List<ISearcher>();
+ controllers.Add(UpnpSearcher.Instance);
+ controllers.Add(PmpSearcher.Instance);
+
+ controllers.ForEach(searcher =>
+ {
+ searcher.DeviceFound += (sender, args) =>
+ {
+ if (DeviceFound != null)
+ DeviceFound(sender, args);
+ };
+ searcher.DeviceLost += (sender, args) =>
+ {
+ if (DeviceLost != null)
+ DeviceLost(sender, args);
+ };
+ });
+ Thread t = new Thread(SearchAndListen);
+ t.IsBackground = true;
+ t.Start();
+ }
+
+ internal static void Log(string format, params object[] args)
+ {
+ var logger = Logger;
+ if (logger != null)
+ logger.Debug(format, args);
+ }
+
+ private static void SearchAndListen()
+ {
+ while (true)
+ {
+ searching.WaitOne();
+
+ try
+ {
+ var enabledProtocols = EnabledProtocols.ToList();
+
+ if (enabledProtocols.Contains(UpnpSearcher.Instance.Protocol))
+ {
+ Receive(UpnpSearcher.Instance, UpnpSearcher.sockets);
+ }
+ if (enabledProtocols.Contains(PmpSearcher.Instance.Protocol))
+ {
+ Receive(PmpSearcher.Instance, PmpSearcher.sockets);
+ }
+
+ foreach (ISearcher s in controllers)
+ if (s.NextSearch < DateTime.Now && enabledProtocols.Contains(s.Protocol))
+ {
+ Log("Searching for: {0}", s.GetType().Name);
+ s.Search();
+ }
+ }
+ catch (Exception e)
+ {
+ if (UnhandledException != null)
+ UnhandledException(typeof(NatUtility), new UnhandledExceptionEventArgs(e, false));
+ }
+ Thread.Sleep(10);
+ }
+ }
+
+ static void Receive (ISearcher searcher, List<UdpClient> clients)
+ {
+ IPEndPoint received = new IPEndPoint(IPAddress.Parse("192.168.0.1"), 5351);
+ foreach (UdpClient client in clients)
+ {
+ if (client.Available > 0)
+ {
+ IPAddress localAddress = ((IPEndPoint)client.Client.LocalEndPoint).Address;
+ byte[] data = client.Receive(ref received);
+ searcher.Handle(localAddress, data, received);
+ }
+ }
+ }
+
+ static void Receive(IMapper mapper, List<UdpClient> clients)
+ {
+ IPEndPoint received = new IPEndPoint(IPAddress.Parse("192.168.0.1"), 5351);
+ foreach (UdpClient client in clients)
+ {
+ if (client.Available > 0)
+ {
+ IPAddress localAddress = ((IPEndPoint)client.Client.LocalEndPoint).Address;
+ byte[] data = client.Receive(ref received);
+ mapper.Handle(localAddress, data);
+ }
+ }
+ }
+
+ public static void StartDiscovery ()
+ {
+ searching.Set();
+ }
+
+ public static void StopDiscovery ()
+ {
+ searching.Reset();
+ }
+
+ //This is for when you know the Gateway IP and want to skip the costly search...
+ public static void DirectMap(IPAddress gatewayAddress, MapperType type)
+ {
+ IMapper mapper;
+ switch (type)
+ {
+ case MapperType.Pmp:
+ mapper = new PmpMapper();
+ break;
+ case MapperType.Upnp:
+ mapper = new UpnpMapper();
+ mapper.DeviceFound += (sender, args) =>
+ {
+ if (DeviceFound != null)
+ DeviceFound(sender, args);
+ };
+ mapper.Map(gatewayAddress);
+ break;
+ default:
+ throw new InvalidOperationException("Unsuported type given");
+
+ }
+ searching.Reset();
+
+ }
+
+ //So then why is it here? -Nick
+ [Obsolete ("This method serves no purpose and shouldn't be used")]
+ public static IPAddress[] GetLocalAddresses (bool includeIPv6)
+ {
+ List<IPAddress> addresses = new List<IPAddress> ();
+
+ IPHostEntry hostInfo = Dns.GetHostEntry (Dns.GetHostName ());
+ foreach (IPAddress address in hostInfo.AddressList) {
+ if (address.AddressFamily == AddressFamily.InterNetwork ||
+ (includeIPv6 && address.AddressFamily == AddressFamily.InterNetworkV6)) {
+ addresses.Add (address);
+ }
+ }
+
+ return addresses.ToArray ();
+ }
+
+ //checks if an IP address is a private address space as defined by RFC 1918
+ public static bool IsPrivateAddressSpace (IPAddress address)
+ {
+ byte[] ba = address.GetAddressBytes ();
+
+ switch ((int)ba[0]) {
+ case 10:
+ return true; //10.x.x.x
+ case 172:
+ return ((int)ba[1] & 16) != 0; //172.16-31.x.x
+ case 192:
+ return (int)ba[1] == 168; //192.168.x.x
+ default:
+ return false;
+ }
+ }
+
+ public static void Handle(IPAddress localAddress, byte[] response, IPEndPoint endpoint, NatProtocol protocol)
+ {
+ switch (protocol)
+ {
+ case NatProtocol.Upnp:
+ UpnpSearcher.Instance.Handle(localAddress, response, endpoint);
+ break;
+ case NatProtocol.Pmp:
+ PmpSearcher.Instance.Handle(localAddress, response, endpoint);
+ break;
+ default:
+ throw new ArgumentException("Unexpected protocol: " + protocol);
+ }
+ }
+
+ public static void Handle(IPAddress localAddress, UpnpDeviceInfo deviceInfo, IPEndPoint endpoint, NatProtocol protocol)
+ {
+ switch (protocol)
+ {
+ case NatProtocol.Upnp:
+ UpnpSearcher.Instance.Handle(localAddress, deviceInfo, endpoint);
+ break;
+ default:
+ throw new ArgumentException("Unexpected protocol: " + protocol);
+ }
+ }
+ }
+}
diff --git a/Mono.Nat/Pmp/AsyncResults/PortMapAsyncResult.cs b/Mono.Nat/Pmp/AsyncResults/PortMapAsyncResult.cs
new file mode 100644
index 000000000..c8ccf5435
--- /dev/null
+++ b/Mono.Nat/Pmp/AsyncResults/PortMapAsyncResult.cs
@@ -0,0 +1,52 @@
+//
+// Authors:
+// Ben Motmans <ben.motmans@gmail.com>
+//
+// Copyright (C) 2007 Ben Motmans
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace Mono.Nat.Pmp
+{
+ internal class PortMapAsyncResult : AsyncResult
+ {
+ private Mapping mapping;
+
+ internal PortMapAsyncResult (Mapping mapping, AsyncCallback callback, object asyncState)
+ : base (callback, asyncState)
+ {
+ this.mapping = mapping;
+ }
+
+ internal PortMapAsyncResult (Protocol protocol, int port, int lifetime, AsyncCallback callback, object asyncState)
+ : base (callback, asyncState)
+ {
+ this.mapping = new Mapping (protocol, port, port, lifetime);
+ }
+
+ internal Mapping Mapping
+ {
+ get { return mapping; }
+ }
+ }
+}
diff --git a/Mono.Nat/Pmp/Mappers/PmpMapper.cs b/Mono.Nat/Pmp/Mappers/PmpMapper.cs
new file mode 100644
index 000000000..f33ca44c3
--- /dev/null
+++ b/Mono.Nat/Pmp/Mappers/PmpMapper.cs
@@ -0,0 +1,83 @@
+//
+// Authors:
+// Nicholas Terry <nick.i.terry@gmail.com>
+//
+// Copyright (C) 2014 Nicholas Terry
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+using Mono.Nat.Pmp;
+
+namespace Mono.Nat.Pmp.Mappers
+{
+ internal class PmpMapper : Pmp, IMapper
+ {
+ public event EventHandler<DeviceEventArgs> DeviceFound;
+
+ static PmpMapper()
+ {
+ CreateSocketsAndAddGateways();
+ }
+
+ public void Map(IPAddress gatewayAddress)
+ {
+ sockets.ForEach(x => Map(x, gatewayAddress));
+ }
+
+ void Map(UdpClient client, IPAddress gatewayAddress)
+ {
+ // The nat-pmp search message. Must be sent to GatewayIP:53531
+ byte[] buffer = new byte[] { PmpConstants.Version, PmpConstants.OperationCode };
+
+ client.Send(buffer, buffer.Length, new IPEndPoint(gatewayAddress, PmpConstants.ServerPort));
+ }
+
+ public void Handle(IPAddress localAddres, byte[] response)
+ {
+ //if (!IsSearchAddress(endpoint.Address))
+ // return;
+ if (response.Length != 12)
+ return;
+ if (response[0] != PmpConstants.Version)
+ return;
+ if (response[1] != PmpConstants.ServerNoop)
+ return;
+ int errorcode = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(response, 2));
+ if (errorcode != 0)
+ NatUtility.Log("Non zero error: {0}", errorcode);
+
+ IPAddress publicIp = new IPAddress(new byte[] { response[8], response[9], response[10], response[11] });
+ OnDeviceFound(new DeviceEventArgs(new PmpNatDevice(localAddres, publicIp)));
+ }
+
+ private void OnDeviceFound(DeviceEventArgs args)
+ {
+ if (DeviceFound != null)
+ DeviceFound(this, args);
+ }
+ }
+}
diff --git a/Mono.Nat/Pmp/Pmp.cs b/Mono.Nat/Pmp/Pmp.cs
new file mode 100644
index 000000000..6795561b1
--- /dev/null
+++ b/Mono.Nat/Pmp/Pmp.cs
@@ -0,0 +1,118 @@
+//
+// Authors:
+// Ben Motmans <ben.motmans@gmail.com>
+// Nicholas Terry <nick.i.terry@gmail.com>
+//
+// Copyright (C) 2007 Ben Motmans
+// Copyright (C) 2014 Nicholas Terry
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.NetworkInformation;
+using System.Net.Sockets;
+using System.Text;
+
+namespace Mono.Nat.Pmp
+{
+ internal abstract class Pmp
+ {
+ public static List<UdpClient> sockets;
+ protected static Dictionary<UdpClient, List<IPEndPoint>> gatewayLists;
+
+ internal static void CreateSocketsAndAddGateways()
+ {
+ sockets = new List<UdpClient>();
+ gatewayLists = new Dictionary<UdpClient, List<IPEndPoint>>();
+
+ try
+ {
+ foreach (NetworkInterface n in NetworkInterface.GetAllNetworkInterfaces())
+ {
+ if (n.OperationalStatus != OperationalStatus.Up && n.OperationalStatus != OperationalStatus.Unknown)
+ continue;
+ IPInterfaceProperties properties = n.GetIPProperties();
+ List<IPEndPoint> gatewayList = new List<IPEndPoint>();
+
+ foreach (GatewayIPAddressInformation gateway in properties.GatewayAddresses)
+ {
+ if (gateway.Address.AddressFamily == AddressFamily.InterNetwork)
+ {
+ gatewayList.Add(new IPEndPoint(gateway.Address, PmpConstants.ServerPort));
+ }
+ }
+ if (gatewayList.Count == 0)
+ {
+ /* Mono on OSX doesn't give any gateway addresses, so check DNS entries */
+ foreach (var gw2 in properties.DnsAddresses)
+ {
+ if (gw2.AddressFamily == AddressFamily.InterNetwork)
+ {
+ gatewayList.Add(new IPEndPoint(gw2, PmpConstants.ServerPort));
+ }
+ }
+ foreach (var unicast in properties.UnicastAddresses)
+ {
+ if (/*unicast.DuplicateAddressDetectionState == DuplicateAddressDetectionState.Preferred
+ && unicast.AddressPreferredLifetime != UInt32.MaxValue
+ && */unicast.Address.AddressFamily == AddressFamily.InterNetwork)
+ {
+ var bytes = unicast.Address.GetAddressBytes();
+ bytes[3] = 1;
+ gatewayList.Add(new IPEndPoint(new IPAddress(bytes), PmpConstants.ServerPort));
+ }
+ }
+ }
+
+ if (gatewayList.Count > 0)
+ {
+ foreach (UnicastIPAddressInformation address in properties.UnicastAddresses)
+ {
+ if (address.Address.AddressFamily == AddressFamily.InterNetwork)
+ {
+ UdpClient client;
+
+ try
+ {
+ client = new UdpClient(new IPEndPoint(address.Address, 0));
+ }
+ catch (SocketException)
+ {
+ continue; // Move on to the next address.
+ }
+
+ gatewayLists.Add(client, gatewayList);
+ sockets.Add(client);
+ }
+ }
+ }
+ }
+ }
+ catch (Exception)
+ {
+ // NAT-PMP does not use multicast, so there isn't really a good fallback.
+ }
+ }
+ }
+}
diff --git a/Mono.Nat/Pmp/PmpConstants.cs b/Mono.Nat/Pmp/PmpConstants.cs
new file mode 100644
index 000000000..ff3eb6230
--- /dev/null
+++ b/Mono.Nat/Pmp/PmpConstants.cs
@@ -0,0 +1,56 @@
+//
+// Authors:
+// Ben Motmans <ben.motmans@gmail.com>
+//
+// Copyright (C) 2007 Ben Motmans
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace Mono.Nat.Pmp
+{
+ internal static class PmpConstants
+ {
+ public const byte Version = (byte)0;
+
+ public const byte OperationCode = (byte)0;
+ public const byte OperationCodeUdp = (byte)1;
+ public const byte OperationCodeTcp = (byte)2;
+ public const byte ServerNoop = (byte)128;
+
+ public const int ClientPort = 5350;
+ public const int ServerPort = 5351;
+
+ public const int RetryDelay = 250;
+ public const int RetryAttempts = 9;
+
+ public const int RecommendedLeaseTime = 60 * 60;
+ public const int DefaultLeaseTime = RecommendedLeaseTime;
+
+ public const short ResultCodeSuccess = 0;
+ public const short ResultCodeUnsupportedVersion = 1;
+ public const short ResultCodeNotAuthorized = 2;
+ public const short ResultCodeNetworkFailure = 3;
+ public const short ResultCodeOutOfResources = 4;
+ public const short ResultCodeUnsupportedOperationCode = 5;
+ }
+} \ No newline at end of file
diff --git a/Mono.Nat/Pmp/PmpNatDevice.cs b/Mono.Nat/Pmp/PmpNatDevice.cs
new file mode 100644
index 000000000..9a2962c4d
--- /dev/null
+++ b/Mono.Nat/Pmp/PmpNatDevice.cs
@@ -0,0 +1,347 @@
+//
+// Authors:
+// Ben Motmans <ben.motmans@gmail.com>
+//
+// Copyright (C) 2007 Ben Motmans
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.IO;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading;
+using System.Collections.Generic;
+
+namespace Mono.Nat.Pmp
+{
+ internal sealed class PmpNatDevice : AbstractNatDevice, IEquatable<PmpNatDevice>
+ {
+ private AsyncResult externalIpResult;
+ private bool pendingOp;
+ private IPAddress localAddress;
+ private IPAddress publicAddress;
+
+ internal PmpNatDevice (IPAddress localAddress, IPAddress publicAddress)
+ {
+ this.localAddress = localAddress;
+ this.publicAddress = publicAddress;
+ }
+
+ public override IPAddress LocalAddress
+ {
+ get { return localAddress; }
+ }
+
+ public override IPAddress GetExternalIP ()
+ {
+ return publicAddress;
+ }
+
+ public override IAsyncResult BeginCreatePortMap(Mapping mapping, AsyncCallback callback, object asyncState)
+ {
+ PortMapAsyncResult pmar = new PortMapAsyncResult (mapping.Protocol, mapping.PublicPort, PmpConstants.DefaultLeaseTime, callback, asyncState);
+ ThreadPool.QueueUserWorkItem (delegate
+ {
+ try
+ {
+ CreatePortMap(pmar.Mapping, true);
+ pmar.Complete();
+ }
+ catch (Exception e)
+ {
+ pmar.Complete(e);
+ }
+ });
+ return pmar;
+ }
+
+ public override IAsyncResult BeginDeletePortMap (Mapping mapping, AsyncCallback callback, object asyncState)
+ {
+ PortMapAsyncResult pmar = new PortMapAsyncResult (mapping, callback, asyncState);
+ ThreadPool.QueueUserWorkItem (delegate {
+ try {
+ CreatePortMap(pmar.Mapping, false);
+ pmar.Complete();
+ } catch (Exception e) {
+ pmar.Complete(e);
+ }
+ });
+ return pmar;
+ }
+
+ public override void EndCreatePortMap (IAsyncResult result)
+ {
+ PortMapAsyncResult pmar = result as PortMapAsyncResult;
+ pmar.AsyncWaitHandle.WaitOne ();
+ }
+
+ public override void EndDeletePortMap (IAsyncResult result)
+ {
+ PortMapAsyncResult pmar = result as PortMapAsyncResult;
+ pmar.AsyncWaitHandle.WaitOne ();
+ }
+
+ public override IAsyncResult BeginGetAllMappings (AsyncCallback callback, object asyncState)
+ {
+ //NAT-PMP does not specify a way to get all port mappings
+ throw new NotSupportedException ();
+ }
+
+ public override IAsyncResult BeginGetExternalIP (AsyncCallback callback, object asyncState)
+ {
+ StartOp(ref externalIpResult, callback, asyncState);
+ AsyncResult result = externalIpResult;
+ result.Complete();
+ return result;
+ }
+
+ public override IAsyncResult BeginGetSpecificMapping (Protocol protocol, int port, AsyncCallback callback, object asyncState)
+ {
+ //NAT-PMP does not specify a way to get a specific port map
+ throw new NotSupportedException ();
+ }
+
+ public override Mapping[] EndGetAllMappings (IAsyncResult result)
+ {
+ //NAT-PMP does not specify a way to get all port mappings
+ throw new NotSupportedException ();
+ }
+
+ public override IPAddress EndGetExternalIP (IAsyncResult result)
+ {
+ EndOp(result, ref externalIpResult);
+ return publicAddress;
+ }
+
+ private void StartOp(ref AsyncResult result, AsyncCallback callback, object asyncState)
+ {
+ if (pendingOp == true)
+ throw new InvalidOperationException("Can only have one simultaenous async operation");
+
+ pendingOp = true;
+ result = new AsyncResult(callback, asyncState);
+ }
+
+ private void EndOp(IAsyncResult supplied, ref AsyncResult actual)
+ {
+ if (supplied == null)
+ throw new ArgumentNullException("result");
+
+ if (supplied != actual)
+ throw new ArgumentException("Supplied IAsyncResult does not match the stored result");
+
+ if (!supplied.IsCompleted)
+ supplied.AsyncWaitHandle.WaitOne();
+
+ if (actual.StoredException != null)
+ throw actual.StoredException;
+
+ pendingOp = false;
+ actual = null;
+ }
+
+ public override Mapping EndGetSpecificMapping (IAsyncResult result)
+ {
+ //NAT-PMP does not specify a way to get a specific port map
+ throw new NotSupportedException ();
+ }
+
+ public override bool Equals(object obj)
+ {
+ PmpNatDevice device = obj as PmpNatDevice;
+ return (device == null) ? false : this.Equals(device);
+ }
+
+ public override int GetHashCode ()
+ {
+ return this.publicAddress.GetHashCode();
+ }
+
+ public bool Equals (PmpNatDevice other)
+ {
+ return (other == null) ? false : this.publicAddress.Equals(other.publicAddress);
+ }
+
+ private Mapping CreatePortMap (Mapping mapping, bool create)
+ {
+ List<byte> package = new List<byte> ();
+
+ package.Add (PmpConstants.Version);
+ package.Add (mapping.Protocol == Protocol.Tcp ? PmpConstants.OperationCodeTcp : PmpConstants.OperationCodeUdp);
+ package.Add ((byte)0); //reserved
+ package.Add ((byte)0); //reserved
+ package.AddRange (BitConverter.GetBytes (IPAddress.HostToNetworkOrder((short)mapping.PrivatePort)));
+ package.AddRange (BitConverter.GetBytes (create ? IPAddress.HostToNetworkOrder((short)mapping.PublicPort) : (short)0));
+ package.AddRange (BitConverter.GetBytes (IPAddress.HostToNetworkOrder(mapping.Lifetime)));
+
+ CreatePortMapAsyncState state = new CreatePortMapAsyncState ();
+ state.Buffer = package.ToArray ();
+ state.Mapping = mapping;
+
+ ThreadPool.QueueUserWorkItem (new WaitCallback (CreatePortMapAsync), state);
+ WaitHandle.WaitAll (new WaitHandle[] {state.ResetEvent});
+
+ if (!state.Success) {
+ string type = create ? "create" : "delete";
+ throw new MappingException (String.Format ("Failed to {0} portmap (protocol={1}, private port={2}", type, mapping.Protocol, mapping.PrivatePort));
+ }
+
+ return state.Mapping;
+ }
+
+ private void CreatePortMapAsync (object obj)
+ {
+ CreatePortMapAsyncState state = obj as CreatePortMapAsyncState;
+
+ UdpClient udpClient = new UdpClient ();
+ CreatePortMapListenState listenState = new CreatePortMapListenState (state, udpClient);
+
+ int attempt = 0;
+ int delay = PmpConstants.RetryDelay;
+
+ ThreadPool.QueueUserWorkItem (new WaitCallback (CreatePortMapListen), listenState);
+
+ while (attempt < PmpConstants.RetryAttempts && !listenState.Success) {
+ udpClient.Send (state.Buffer, state.Buffer.Length, new IPEndPoint (localAddress, PmpConstants.ServerPort));
+ listenState.UdpClientReady.Set();
+
+ attempt++;
+ delay *= 2;
+ Thread.Sleep (delay);
+ }
+
+ state.Success = listenState.Success;
+
+ udpClient.Close ();
+ state.ResetEvent.Set ();
+ }
+
+ private void CreatePortMapListen (object obj)
+ {
+ CreatePortMapListenState state = obj as CreatePortMapListenState;
+
+ UdpClient udpClient = state.UdpClient;
+ state.UdpClientReady.WaitOne(); // Evidently UdpClient has some lazy-init Send/Receive race?
+ IPEndPoint endPoint = new IPEndPoint (localAddress, PmpConstants.ServerPort);
+
+ while (!state.Success)
+ {
+ byte[] data;
+ try
+ {
+ data = udpClient.Receive(ref endPoint);
+ }
+ catch (SocketException)
+ {
+ state.Success = false;
+ return;
+ }
+
+ catch (ObjectDisposedException)
+ {
+ state.Success = false;
+ return;
+ }
+
+ if (data.Length < 16)
+ continue;
+
+ if (data[0] != PmpConstants.Version)
+ continue;
+
+ byte opCode = (byte)(data[1] & (byte)127);
+
+ Protocol protocol = Protocol.Tcp;
+ if (opCode == PmpConstants.OperationCodeUdp)
+ protocol = Protocol.Udp;
+
+ short resultCode = IPAddress.NetworkToHostOrder (BitConverter.ToInt16 (data, 2));
+ uint epoch = (uint)IPAddress.NetworkToHostOrder (BitConverter.ToInt32 (data, 4));
+
+ int privatePort = IPAddress.NetworkToHostOrder (BitConverter.ToInt16 (data, 8));
+ int publicPort = IPAddress.NetworkToHostOrder (BitConverter.ToInt16 (data, 10));
+
+ uint lifetime = (uint)IPAddress.NetworkToHostOrder (BitConverter.ToInt32 (data, 12));
+
+ if (publicPort < 0 || privatePort < 0 || resultCode != PmpConstants.ResultCodeSuccess)
+ {
+ state.Success = false;
+ return;
+ }
+
+ if (lifetime == 0)
+ {
+ //mapping was deleted
+ state.Success = true;
+ state.Mapping = null;
+ return;
+ }
+ else
+ {
+ //mapping was created
+ //TODO: verify that the private port+protocol are a match
+ Mapping mapping = state.Mapping;
+ mapping.PublicPort = publicPort;
+ mapping.Protocol = protocol;
+ mapping.Expiration = DateTime.Now.AddSeconds (lifetime);
+
+ state.Success = true;
+ }
+ }
+ }
+
+
+ /// <summary>
+ /// Overridden.
+ /// </summary>
+ /// <returns></returns>
+ public override string ToString( )
+ {
+ return String.Format( "PmpNatDevice - Local Address: {0}, Public IP: {1}, Last Seen: {2}",
+ this.localAddress, this.publicAddress, this.LastSeen );
+ }
+
+
+ private class CreatePortMapAsyncState
+ {
+ internal byte[] Buffer;
+ internal ManualResetEvent ResetEvent = new ManualResetEvent (false);
+ internal Mapping Mapping;
+
+ internal bool Success;
+ }
+
+ private class CreatePortMapListenState
+ {
+ internal volatile bool Success;
+ internal Mapping Mapping;
+ internal UdpClient UdpClient;
+ internal ManualResetEvent UdpClientReady;
+
+ internal CreatePortMapListenState (CreatePortMapAsyncState state, UdpClient client)
+ {
+ Mapping = state.Mapping;
+ UdpClient = client; UdpClientReady = new ManualResetEvent(false);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Mono.Nat/Pmp/Searchers/PmpSearcher.cs b/Mono.Nat/Pmp/Searchers/PmpSearcher.cs
new file mode 100644
index 000000000..df0273ccb
--- /dev/null
+++ b/Mono.Nat/Pmp/Searchers/PmpSearcher.cs
@@ -0,0 +1,149 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+// Ben Motmans <ben.motmans@gmail.com>
+// Nicholas Terry <nick.i.terry@gmail.com>
+//
+// Copyright (C) 2006 Alan McGovern
+// Copyright (C) 2007 Ben Motmans
+// Copyright (C) 2014 Nicholas Terry
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Net;
+using Mono.Nat.Pmp;
+using System.Net.NetworkInformation;
+using System.Net.Sockets;
+using System.Linq;
+
+namespace Mono.Nat
+{
+ internal class PmpSearcher : Pmp.Pmp, ISearcher
+ {
+ static PmpSearcher instance = new PmpSearcher();
+
+
+ public static PmpSearcher Instance
+ {
+ get { return instance; }
+ }
+
+ private int timeout;
+ private DateTime nextSearch;
+ public event EventHandler<DeviceEventArgs> DeviceFound;
+ public event EventHandler<DeviceEventArgs> DeviceLost;
+
+ static PmpSearcher()
+ {
+ CreateSocketsAndAddGateways();
+ }
+
+ PmpSearcher()
+ {
+ timeout = 250;
+ }
+
+ public void Search()
+ {
+ foreach (UdpClient s in sockets)
+ {
+ try
+ {
+ Search(s);
+ }
+ catch
+ {
+ // Ignore any search errors
+ }
+ }
+ }
+
+ void Search (UdpClient client)
+ {
+ // Sort out the time for the next search first. The spec says the
+ // timeout should double after each attempt. Once it reaches 64 seconds
+ // (and that attempt fails), assume no devices available
+ nextSearch = DateTime.Now.AddMilliseconds(timeout);
+ timeout *= 2;
+
+ // We've tried 9 times as per spec, try searching again in 5 minutes
+ if (timeout == 128 * 1000)
+ {
+ timeout = 250;
+ nextSearch = DateTime.Now.AddMinutes(10);
+ return;
+ }
+
+ // The nat-pmp search message. Must be sent to GatewayIP:53531
+ byte[] buffer = new byte[] { PmpConstants.Version, PmpConstants.OperationCode };
+ foreach (IPEndPoint gatewayEndpoint in gatewayLists[client])
+ client.Send(buffer, buffer.Length, gatewayEndpoint);
+ }
+
+ bool IsSearchAddress(IPAddress address)
+ {
+ foreach (List<IPEndPoint> gatewayList in gatewayLists.Values)
+ foreach (IPEndPoint gatewayEndpoint in gatewayList)
+ if (gatewayEndpoint.Address.Equals(address))
+ return true;
+ return false;
+ }
+
+ public void Handle(IPAddress localAddress, byte[] response, IPEndPoint endpoint)
+ {
+ if (!IsSearchAddress(endpoint.Address))
+ return;
+ if (response.Length != 12)
+ return;
+ if (response[0] != PmpConstants.Version)
+ return;
+ if (response[1] != PmpConstants.ServerNoop)
+ return;
+ int errorcode = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(response, 2));
+ if (errorcode != 0)
+ NatUtility.Log("Non zero error: {0}", errorcode);
+
+ IPAddress publicIp = new IPAddress(new byte[] { response[8], response[9], response[10], response[11] });
+ nextSearch = DateTime.Now.AddMinutes(5);
+ timeout = 250;
+ OnDeviceFound(new DeviceEventArgs(new PmpNatDevice(endpoint.Address, publicIp)));
+ }
+
+ public DateTime NextSearch
+ {
+ get { return nextSearch; }
+ }
+ private void OnDeviceFound(DeviceEventArgs args)
+ {
+ if (DeviceFound != null)
+ DeviceFound(this, args);
+ }
+
+ public NatProtocol Protocol
+ {
+ get { return NatProtocol.Pmp; }
+ }
+ }
+}
diff --git a/Mono.Nat/Properties/AssemblyInfo.cs b/Mono.Nat/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..c3c3101de
--- /dev/null
+++ b/Mono.Nat/Properties/AssemblyInfo.cs
@@ -0,0 +1,31 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Mono.Nat")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Mono.Nat")]
+[assembly: AssemblyCopyright("Copyright © 2016")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("d7453b88-2266-4805-b39b-2b5a2a33e1ba")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+// \ No newline at end of file
diff --git a/Mono.Nat/Upnp/AsyncResults/GetAllMappingsAsyncResult.cs b/Mono.Nat/Upnp/AsyncResults/GetAllMappingsAsyncResult.cs
new file mode 100644
index 000000000..51ecfbaf0
--- /dev/null
+++ b/Mono.Nat/Upnp/AsyncResults/GetAllMappingsAsyncResult.cs
@@ -0,0 +1,56 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Net;
+
+namespace Mono.Nat.Upnp
+{
+ internal class GetAllMappingsAsyncResult : PortMapAsyncResult
+ {
+ private List<Mapping> mappings;
+ private Mapping specificMapping;
+
+ public GetAllMappingsAsyncResult(WebRequest request, AsyncCallback callback, object asyncState)
+ : base(request, callback, asyncState)
+ {
+ mappings = new List<Mapping>();
+ }
+
+ public List<Mapping> Mappings
+ {
+ get { return this.mappings; }
+ }
+
+ public Mapping SpecificMapping
+ {
+ get { return this.specificMapping; }
+ set { this.specificMapping = value; }
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/AsyncResults/PortMapAsyncResult.cs b/Mono.Nat/Upnp/AsyncResults/PortMapAsyncResult.cs
new file mode 100644
index 000000000..d8ac3fe61
--- /dev/null
+++ b/Mono.Nat/Upnp/AsyncResults/PortMapAsyncResult.cs
@@ -0,0 +1,75 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+
+
+using System;
+using System.Net;
+using System.Threading;
+
+namespace Mono.Nat.Upnp
+{
+ internal class PortMapAsyncResult : AsyncResult
+ {
+ private WebRequest request;
+ private MessageBase savedMessage;
+
+ protected PortMapAsyncResult(WebRequest request, AsyncCallback callback, object asyncState)
+ : base (callback, asyncState)
+ {
+ this.request = request;
+ }
+
+ internal WebRequest Request
+ {
+ get { return this.request; }
+ set { this.request = value; }
+ }
+
+ internal MessageBase SavedMessage
+ {
+ get { return this.savedMessage; }
+ set { this.savedMessage = value; }
+ }
+
+ internal static PortMapAsyncResult Create (MessageBase message, WebRequest request, AsyncCallback storedCallback, object asyncState)
+ {
+ if (message is GetGenericPortMappingEntry)
+ return new GetAllMappingsAsyncResult(request, storedCallback, asyncState);
+
+ if (message is GetSpecificPortMappingEntryMessage)
+ {
+ GetSpecificPortMappingEntryMessage mapMessage = (GetSpecificPortMappingEntryMessage)message;
+ GetAllMappingsAsyncResult result = new GetAllMappingsAsyncResult(request, storedCallback, asyncState);
+
+ result.SpecificMapping = new Mapping(mapMessage.protocol, 0, mapMessage.externalPort, 0);
+ return result;
+ }
+
+ return new PortMapAsyncResult(request, storedCallback, asyncState);
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Mappers/UpnpMapper.cs b/Mono.Nat/Upnp/Mappers/UpnpMapper.cs
new file mode 100644
index 000000000..6f2716805
--- /dev/null
+++ b/Mono.Nat/Upnp/Mappers/UpnpMapper.cs
@@ -0,0 +1,110 @@
+//
+// Authors:
+// Nicholas Terry <nick.i.terry@gmail.com>
+//
+// Copyright (C) 2014 Nicholas Terry
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading;
+
+namespace Mono.Nat.Upnp.Mappers
+{
+ internal class UpnpMapper : Upnp, IMapper
+ {
+
+ public event EventHandler<DeviceEventArgs> DeviceFound;
+
+ public UdpClient Client { get; set; }
+
+ public UpnpMapper()
+ {
+ //Bind to local port 1900 for ssdp responses
+ Client = new UdpClient(1900);
+ }
+
+ public void Map(IPAddress gatewayAddress)
+ {
+ //Get the httpu request payload
+ byte[] data = DiscoverDeviceMessage.EncodeUnicast(gatewayAddress);
+
+ Client.Send(data, data.Length, new IPEndPoint(gatewayAddress, 1900));
+
+ new Thread(Receive).Start();
+ }
+
+ public void Receive()
+ {
+ while (true)
+ {
+ IPEndPoint received = new IPEndPoint(IPAddress.Parse("192.168.0.1"), 5351);
+ if (Client.Available > 0)
+ {
+ IPAddress localAddress = ((IPEndPoint)Client.Client.LocalEndPoint).Address;
+ byte[] data = Client.Receive(ref received);
+ Handle(localAddress, data, received);
+ }
+ }
+ }
+
+ public void Handle(IPAddress localAddres, byte[] response)
+ {
+ Handle(localAddres, response, null);
+ }
+
+ public void Handle(IPAddress localAddress, byte[] response, IPEndPoint endpoint)
+ {
+ // No matter what, this method should never throw an exception. If something goes wrong
+ // we should still be in a position to handle the next reply correctly.
+ try
+ {
+ UpnpNatDevice d = base.Handle(localAddress, response, endpoint);
+ d.GetServicesList(DeviceSetupComplete);
+ }
+ catch (Exception ex)
+ {
+ Trace.WriteLine("Unhandled exception when trying to decode a device's response Send me the following data: ");
+ Trace.WriteLine("ErrorMessage:");
+ Trace.WriteLine(ex.Message);
+ Trace.WriteLine("Data string:");
+ Trace.WriteLine(Encoding.UTF8.GetString(response));
+ }
+ }
+
+ private void DeviceSetupComplete(INatDevice device)
+ {
+ OnDeviceFound(new DeviceEventArgs(device));
+ }
+
+ private void OnDeviceFound(DeviceEventArgs args)
+ {
+ if (DeviceFound != null)
+ DeviceFound(this, args);
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Messages/DiscoverDeviceMessage.cs b/Mono.Nat/Upnp/Messages/DiscoverDeviceMessage.cs
new file mode 100644
index 000000000..87f5835a6
--- /dev/null
+++ b/Mono.Nat/Upnp/Messages/DiscoverDeviceMessage.cs
@@ -0,0 +1,60 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System.Net;
+using System.Text;
+
+namespace Mono.Nat.Upnp
+{
+ internal static class DiscoverDeviceMessage
+ {
+ /// <summary>
+ /// The message sent to discover all uPnP devices on the network
+ /// </summary>
+ /// <returns></returns>
+ public static byte[] EncodeSSDP()
+ {
+ string s = "M-SEARCH * HTTP/1.1\r\n"
+ + "HOST: 239.255.255.250:1900\r\n"
+ + "MAN: \"ssdp:discover\"\r\n"
+ + "MX: 3\r\n"
+ + "ST: ssdp:all\r\n\r\n";
+ return UTF8Encoding.ASCII.GetBytes(s);
+ }
+
+ public static byte[] EncodeUnicast(IPAddress gatewayAddress)
+ {
+ //Format obtained from http://upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.1.pdf pg 31
+ //This method only works with upnp 1.1 routers... unfortunately
+ string s = "M-SEARCH * HTTP/1.1\r\n"
+ + "HOST: " + gatewayAddress + ":1900\r\n"
+ + "MAN: \"ssdp:discover\"\r\n"
+ + "ST: ssdp:all\r\n\r\n";
+ //+ "USER-AGENT: unix/5.1 UPnP/1.1 MyProduct/1.0\r\n\r\n";
+ return UTF8Encoding.ASCII.GetBytes(s);
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Messages/ErrorMessage.cs b/Mono.Nat/Upnp/Messages/ErrorMessage.cs
new file mode 100644
index 000000000..ce5270e9b
--- /dev/null
+++ b/Mono.Nat/Upnp/Messages/ErrorMessage.cs
@@ -0,0 +1,63 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace Mono.Nat.Upnp
+{
+ internal class ErrorMessage : MessageBase
+ {
+ #region Member Variables
+ public string Description
+ {
+ get { return this.description; }
+ }
+ private string description;
+
+ public int ErrorCode
+ {
+ get { return this.errorCode; }
+ }
+ private int errorCode;
+ #endregion
+
+
+ #region Constructors
+ public ErrorMessage(int errorCode, string description)
+ :base(null)
+ {
+ this.description = description;
+ this.errorCode = errorCode;
+ }
+ #endregion
+
+
+ public override System.Net.WebRequest Encode(out byte[] body)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Messages/GetServicesMessage.cs b/Mono.Nat/Upnp/Messages/GetServicesMessage.cs
new file mode 100644
index 000000000..c5d7bce70
--- /dev/null
+++ b/Mono.Nat/Upnp/Messages/GetServicesMessage.cs
@@ -0,0 +1,62 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Diagnostics;
+using System.Net;
+
+namespace Mono.Nat.Upnp
+{
+ internal class GetServicesMessage : MessageBase
+ {
+ private string servicesDescriptionUrl;
+ private EndPoint hostAddress;
+
+ public GetServicesMessage(string description, EndPoint hostAddress)
+ :base(null)
+ {
+ if (string.IsNullOrEmpty(description))
+ Trace.WriteLine("Description is null");
+
+ if (hostAddress == null)
+ Trace.WriteLine("hostaddress is null");
+
+ this.servicesDescriptionUrl = description;
+ this.hostAddress = hostAddress;
+ }
+
+
+ public override WebRequest Encode(out byte[] body)
+ {
+ HttpWebRequest req = (HttpWebRequest)WebRequest.Create("http://" + this.hostAddress.ToString() + this.servicesDescriptionUrl);
+ req.Headers.Add("ACCEPT-LANGUAGE", "en");
+ req.Method = "GET";
+
+ body = new byte[0];
+ return req;
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Messages/Requests/CreatePortMappingMessage.cs b/Mono.Nat/Upnp/Messages/Requests/CreatePortMappingMessage.cs
new file mode 100644
index 000000000..da650fb41
--- /dev/null
+++ b/Mono.Nat/Upnp/Messages/Requests/CreatePortMappingMessage.cs
@@ -0,0 +1,75 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System.Net;
+using System.IO;
+using System.Globalization;
+using System.Text;
+using System.Xml;
+
+namespace Mono.Nat.Upnp
+{
+ internal class CreatePortMappingMessage : MessageBase
+ {
+ #region Private Fields
+
+ private IPAddress localIpAddress;
+ private Mapping mapping;
+
+ #endregion
+
+
+ #region Constructors
+ public CreatePortMappingMessage(Mapping mapping, IPAddress localIpAddress, UpnpNatDevice device)
+ : base(device)
+ {
+ this.mapping = mapping;
+ this.localIpAddress = localIpAddress;
+ }
+ #endregion
+
+
+ public override WebRequest Encode(out byte[] body)
+ {
+ CultureInfo culture = CultureInfo.InvariantCulture;
+
+ StringBuilder builder = new StringBuilder(256);
+ XmlWriter writer = CreateWriter(builder);
+
+ WriteFullElement(writer, "NewRemoteHost", string.Empty);
+ WriteFullElement(writer, "NewExternalPort", this.mapping.PublicPort.ToString(culture));
+ WriteFullElement(writer, "NewProtocol", this.mapping.Protocol == Protocol.Tcp ? "TCP" : "UDP");
+ WriteFullElement(writer, "NewInternalPort", this.mapping.PrivatePort.ToString(culture));
+ WriteFullElement(writer, "NewInternalClient", this.localIpAddress.ToString());
+ WriteFullElement(writer, "NewEnabled", "1");
+ WriteFullElement(writer, "NewPortMappingDescription", string.IsNullOrEmpty(mapping.Description) ? "Mono.Nat" : mapping.Description);
+ WriteFullElement(writer, "NewLeaseDuration", mapping.Lifetime.ToString());
+
+ writer.Flush();
+ return CreateRequest("AddPortMapping", builder.ToString(), out body);
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Messages/Requests/DeletePortMappingMessage.cs b/Mono.Nat/Upnp/Messages/Requests/DeletePortMappingMessage.cs
new file mode 100644
index 000000000..d9be89a69
--- /dev/null
+++ b/Mono.Nat/Upnp/Messages/Requests/DeletePortMappingMessage.cs
@@ -0,0 +1,57 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System.Net;
+using System.IO;
+using System.Text;
+using System.Xml;
+
+namespace Mono.Nat.Upnp
+{
+ internal class DeletePortMappingMessage : MessageBase
+ {
+ private Mapping mapping;
+
+ public DeletePortMappingMessage(Mapping mapping, UpnpNatDevice device)
+ : base(device)
+ {
+ this.mapping = mapping;
+ }
+
+ public override WebRequest Encode(out byte[] body)
+ {
+ StringBuilder builder = new StringBuilder(256);
+ XmlWriter writer = CreateWriter(builder);
+
+ WriteFullElement(writer, "NewRemoteHost", string.Empty);
+ WriteFullElement(writer, "NewExternalPort", mapping.PublicPort.ToString(MessageBase.Culture));
+ WriteFullElement(writer, "NewProtocol", mapping.Protocol == Protocol.Tcp ? "TCP" : "UDP");
+
+ writer.Flush();
+ return CreateRequest("DeletePortMapping", builder.ToString(), out body);
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Messages/Requests/GetExternalIPAddressMessage.cs b/Mono.Nat/Upnp/Messages/Requests/GetExternalIPAddressMessage.cs
new file mode 100644
index 000000000..8f97002ea
--- /dev/null
+++ b/Mono.Nat/Upnp/Messages/Requests/GetExternalIPAddressMessage.cs
@@ -0,0 +1,51 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Net;
+using System.IO;
+
+namespace Mono.Nat.Upnp
+{
+ internal class GetExternalIPAddressMessage : MessageBase
+ {
+
+ #region Constructors
+ public GetExternalIPAddressMessage(UpnpNatDevice device)
+ :base(device)
+ {
+ }
+ #endregion
+
+
+ public override WebRequest Encode(out byte[] body)
+ {
+ return CreateRequest("GetExternalIPAddress", string.Empty, out body);
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Messages/Requests/GetGenericPortMappingEntry.cs b/Mono.Nat/Upnp/Messages/Requests/GetGenericPortMappingEntry.cs
new file mode 100644
index 000000000..c0c555881
--- /dev/null
+++ b/Mono.Nat/Upnp/Messages/Requests/GetGenericPortMappingEntry.cs
@@ -0,0 +1,55 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Xml;
+
+namespace Mono.Nat.Upnp
+{
+ internal class GetGenericPortMappingEntry : MessageBase
+ {
+ private int index;
+
+ public GetGenericPortMappingEntry(int index, UpnpNatDevice device)
+ :base(device)
+ {
+ this.index = index;
+ }
+
+ public override System.Net.WebRequest Encode(out byte[] body)
+ {
+ StringBuilder sb = new StringBuilder(128);
+ XmlWriter writer = CreateWriter(sb);
+
+ WriteFullElement(writer, "NewPortMappingIndex", index.ToString());
+
+ writer.Flush();
+ return CreateRequest("GetGenericPortMappingEntry", sb.ToString(), out body);
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Messages/Requests/GetSpecificPortMappingEntryMessage.cs b/Mono.Nat/Upnp/Messages/Requests/GetSpecificPortMappingEntryMessage.cs
new file mode 100644
index 000000000..314468ece
--- /dev/null
+++ b/Mono.Nat/Upnp/Messages/Requests/GetSpecificPortMappingEntryMessage.cs
@@ -0,0 +1,60 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Xml;
+using System.Net;
+
+namespace Mono.Nat.Upnp
+{
+ internal class GetSpecificPortMappingEntryMessage : MessageBase
+ {
+ internal Protocol protocol;
+ internal int externalPort;
+
+ public GetSpecificPortMappingEntryMessage(Protocol protocol, int externalPort, UpnpNatDevice device)
+ : base(device)
+ {
+ this.protocol = protocol;
+ this.externalPort = externalPort;
+ }
+
+ public override WebRequest Encode(out byte[] body)
+ {
+ StringBuilder sb = new StringBuilder(64);
+ XmlWriter writer = CreateWriter(sb);
+
+ WriteFullElement(writer, "NewRemoteHost", string.Empty);
+ WriteFullElement(writer, "NewExternalPort", externalPort.ToString());
+ WriteFullElement(writer, "NewProtocol", protocol == Protocol.Tcp ? "TCP" : "UDP");
+ writer.Flush();
+
+ return CreateRequest("GetSpecificPortMappingEntry", sb.ToString(), out body);
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Messages/Responses/CreatePortMappingResponseMessage.cs b/Mono.Nat/Upnp/Messages/Responses/CreatePortMappingResponseMessage.cs
new file mode 100644
index 000000000..e75926b09
--- /dev/null
+++ b/Mono.Nat/Upnp/Messages/Responses/CreatePortMappingResponseMessage.cs
@@ -0,0 +1,46 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+
+
+using System;
+namespace Mono.Nat.Upnp
+{
+ internal class CreatePortMappingResponseMessage : MessageBase
+ {
+ #region Constructors
+ public CreatePortMappingResponseMessage()
+ :base(null)
+ {
+ }
+ #endregion
+
+ public override System.Net.WebRequest Encode(out byte[] body)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Messages/Responses/DeletePortMappingResponseMessage.cs b/Mono.Nat/Upnp/Messages/Responses/DeletePortMappingResponseMessage.cs
new file mode 100644
index 000000000..1fce4eb04
--- /dev/null
+++ b/Mono.Nat/Upnp/Messages/Responses/DeletePortMappingResponseMessage.cs
@@ -0,0 +1,44 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+
+
+using System;
+namespace Mono.Nat.Upnp
+{
+ internal class DeletePortMapResponseMessage : MessageBase
+ {
+ public DeletePortMapResponseMessage()
+ :base(null)
+ {
+ }
+
+ public override System.Net.WebRequest Encode(out byte[] body)
+ {
+ throw new NotSupportedException();
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Messages/Responses/GetExternalIPAddressResponseMessage.cs b/Mono.Nat/Upnp/Messages/Responses/GetExternalIPAddressResponseMessage.cs
new file mode 100644
index 000000000..ee4b18cd1
--- /dev/null
+++ b/Mono.Nat/Upnp/Messages/Responses/GetExternalIPAddressResponseMessage.cs
@@ -0,0 +1,53 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Net;
+
+namespace Mono.Nat.Upnp
+{
+ internal class GetExternalIPAddressResponseMessage : MessageBase
+ {
+ public IPAddress ExternalIPAddress
+ {
+ get { return this.externalIPAddress; }
+ }
+ private IPAddress externalIPAddress;
+
+ public GetExternalIPAddressResponseMessage(string ip)
+ :base(null)
+ {
+ this.externalIPAddress = IPAddress.Parse(ip);
+ }
+
+ public override WebRequest Encode(out byte[] body)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Messages/Responses/GetGenericPortMappingEntryResponseMessage.cs b/Mono.Nat/Upnp/Messages/Responses/GetGenericPortMappingEntryResponseMessage.cs
new file mode 100644
index 000000000..b11bfa027
--- /dev/null
+++ b/Mono.Nat/Upnp/Messages/Responses/GetGenericPortMappingEntryResponseMessage.cs
@@ -0,0 +1,108 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Xml;
+
+namespace Mono.Nat.Upnp
+{
+ internal class GetGenericPortMappingEntryResponseMessage : MessageBase
+ {
+ private string remoteHost;
+ private int externalPort;
+ private Protocol protocol;
+ private int internalPort;
+ private string internalClient;
+ private bool enabled;
+ private string portMappingDescription;
+ private int leaseDuration;
+
+ public string RemoteHost
+ {
+ get { return this.remoteHost; }
+ }
+
+ public int ExternalPort
+ {
+ get { return this.externalPort; }
+ }
+
+ public Protocol Protocol
+ {
+ get { return this.protocol; }
+ }
+
+ public int InternalPort
+ {
+ get { return this.internalPort; }
+ }
+
+ public string InternalClient
+ {
+ get { return this.internalClient; }
+ }
+
+ public bool Enabled
+ {
+ get { return this.enabled; }
+ }
+
+ public string PortMappingDescription
+ {
+ get { return this.portMappingDescription; }
+ }
+
+ public int LeaseDuration
+ {
+ get { return this.leaseDuration; }
+ }
+
+
+ public GetGenericPortMappingEntryResponseMessage(XmlNode data, bool genericMapping)
+ : base(null)
+ {
+ remoteHost = (genericMapping) ? data["NewRemoteHost"].InnerText : string.Empty;
+ externalPort = (genericMapping) ? Convert.ToInt32(data["NewExternalPort"].InnerText) : -1;
+ if (genericMapping)
+ protocol = data["NewProtocol"].InnerText.Equals("TCP", StringComparison.InvariantCultureIgnoreCase) ? Protocol.Tcp : Protocol.Udp;
+ else
+ protocol = Protocol.Udp;
+
+ internalPort = Convert.ToInt32(data["NewInternalPort"].InnerText);
+ internalClient = data["NewInternalClient"].InnerText;
+ enabled = data["NewEnabled"].InnerText == "1" ? true : false;
+ portMappingDescription = data["NewPortMappingDescription"].InnerText;
+ leaseDuration = Convert.ToInt32(data["NewLeaseDuration"].InnerText);
+ }
+
+ public override System.Net.WebRequest Encode(out byte[] body)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Messages/UpnpMessage.cs b/Mono.Nat/Upnp/Messages/UpnpMessage.cs
new file mode 100644
index 000000000..44c16eec6
--- /dev/null
+++ b/Mono.Nat/Upnp/Messages/UpnpMessage.cs
@@ -0,0 +1,132 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Diagnostics;
+using System.Xml;
+using System.Net;
+using System.IO;
+using System.Text;
+using System.Globalization;
+
+namespace Mono.Nat.Upnp
+{
+ internal abstract class MessageBase
+ {
+ internal static readonly CultureInfo Culture = CultureInfo.InvariantCulture;
+ protected UpnpNatDevice device;
+
+ protected MessageBase(UpnpNatDevice device)
+ {
+ this.device = device;
+ }
+
+ protected WebRequest CreateRequest(string upnpMethod, string methodParameters, out byte[] body)
+ {
+ string ss = "http://" + this.device.HostEndPoint.ToString() + this.device.ControlUrl;
+ NatUtility.Log("Initiating request to: {0}", ss);
+ Uri location = new Uri(ss);
+
+ HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(location);
+ req.KeepAlive = false;
+ req.Method = "POST";
+ req.ContentType = "text/xml; charset=\"utf-8\"";
+ req.Headers.Add("SOAPACTION", "\"" + device.ServiceType + "#" + upnpMethod + "\"");
+
+ string bodyString = "<s:Envelope "
+ + "xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
+ + "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
+ + "<s:Body>"
+ + "<u:" + upnpMethod + " "
+ + "xmlns:u=\"" + device.ServiceType + "\">"
+ + methodParameters
+ + "</u:" + upnpMethod + ">"
+ + "</s:Body>"
+ + "</s:Envelope>\r\n\r\n";
+
+ body = System.Text.Encoding.UTF8.GetBytes(bodyString);
+ return req;
+ }
+
+ public static MessageBase Decode(UpnpNatDevice device, string message)
+ {
+ XmlNode node;
+ XmlDocument doc = new XmlDocument();
+ doc.LoadXml(message);
+
+ XmlNamespaceManager nsm = new XmlNamespaceManager(doc.NameTable);
+
+ // Error messages should be found under this namespace
+ nsm.AddNamespace("errorNs", "urn:schemas-upnp-org:control-1-0");
+ nsm.AddNamespace("responseNs", device.ServiceType);
+
+ // Check to see if we have a fault code message.
+ if ((node = doc.SelectSingleNode("//errorNs:UPnPError", nsm)) != null) {
+ string errorCode = node["errorCode"] != null ? node["errorCode"].InnerText : "";
+ string errorDescription = node["errorDescription"] != null ? node["errorDescription"].InnerText : "";
+
+ return new ErrorMessage(Convert.ToInt32(errorCode, CultureInfo.InvariantCulture), errorDescription);
+ }
+
+ if ((doc.SelectSingleNode("//responseNs:AddPortMappingResponse", nsm)) != null)
+ return new CreatePortMappingResponseMessage();
+
+ if ((doc.SelectSingleNode("//responseNs:DeletePortMappingResponse", nsm)) != null)
+ return new DeletePortMapResponseMessage();
+
+ if ((node = doc.SelectSingleNode("//responseNs:GetExternalIPAddressResponse", nsm)) != null) {
+ string newExternalIPAddress = node["NewExternalIPAddress"] != null ? node["NewExternalIPAddress"].InnerText : "";
+ return new GetExternalIPAddressResponseMessage(newExternalIPAddress);
+ }
+
+ if ((node = doc.SelectSingleNode("//responseNs:GetGenericPortMappingEntryResponse", nsm)) != null)
+ return new GetGenericPortMappingEntryResponseMessage(node, true);
+
+ if ((node = doc.SelectSingleNode("//responseNs:GetSpecificPortMappingEntryResponse", nsm)) != null)
+ return new GetGenericPortMappingEntryResponseMessage(node, false);
+
+ NatUtility.Log("Unknown message returned. Please send me back the following XML:");
+ NatUtility.Log(message);
+ return null;
+ }
+
+ public abstract WebRequest Encode(out byte[] body);
+
+ internal static void WriteFullElement(XmlWriter writer, string element, string value)
+ {
+ writer.WriteStartElement(element);
+ writer.WriteString(value);
+ writer.WriteEndElement();
+ }
+
+ internal static XmlWriter CreateWriter(StringBuilder sb)
+ {
+ XmlWriterSettings settings = new XmlWriterSettings();
+ settings.ConformanceLevel = ConformanceLevel.Fragment;
+ return XmlWriter.Create(sb, settings);
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Searchers/UpnpSearcher.cs b/Mono.Nat/Upnp/Searchers/UpnpSearcher.cs
new file mode 100644
index 000000000..edc5a5d76
--- /dev/null
+++ b/Mono.Nat/Upnp/Searchers/UpnpSearcher.cs
@@ -0,0 +1,287 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+// Ben Motmans <ben.motmans@gmail.com>
+// Nicholas Terry <nick.i.terry@gmail.com>
+//
+// Copyright (C) 2006 Alan McGovern
+// Copyright (C) 2007 Ben Motmans
+// Copyright (C) 2014 Nicholas Terry
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Net;
+using Mono.Nat.Upnp;
+using System.Diagnostics;
+using System.Net.Sockets;
+using System.Net.NetworkInformation;
+using MediaBrowser.Controller.Dlna;
+
+namespace Mono.Nat
+{
+ internal class UpnpSearcher : ISearcher
+ {
+ private const int SearchPeriod = 5 * 60; // The time in seconds between each search
+ static UpnpSearcher instance = new UpnpSearcher();
+ public static List<UdpClient> sockets = CreateSockets();
+
+ public static UpnpSearcher Instance
+ {
+ get { return instance; }
+ }
+
+ public event EventHandler<DeviceEventArgs> DeviceFound;
+ public event EventHandler<DeviceEventArgs> DeviceLost;
+
+ private List<INatDevice> devices;
+ private Dictionary<IPAddress, DateTime> lastFetched;
+ private DateTime nextSearch;
+ private IPEndPoint searchEndpoint;
+
+ UpnpSearcher()
+ {
+ devices = new List<INatDevice>();
+ lastFetched = new Dictionary<IPAddress, DateTime>();
+ //searchEndpoint = new IPEndPoint(IPAddress.Parse("239.255.255.250"), 1900);
+ searchEndpoint = new IPEndPoint(IPAddress.Parse("239.255.255.250"), 1900);
+ }
+
+ static List<UdpClient> CreateSockets()
+ {
+ List<UdpClient> clients = new List<UdpClient>();
+ try
+ {
+ foreach (NetworkInterface n in NetworkInterface.GetAllNetworkInterfaces())
+ {
+ foreach (UnicastIPAddressInformation address in n.GetIPProperties().UnicastAddresses)
+ {
+ if (address.Address.AddressFamily == AddressFamily.InterNetwork)
+ {
+ try
+ {
+ clients.Add(new UdpClient(new IPEndPoint(address.Address, 0)));
+ }
+ catch
+ {
+ continue; // Move on to the next address.
+ }
+ }
+ }
+ }
+ }
+ catch (Exception)
+ {
+ clients.Add(new UdpClient(0));
+ }
+ return clients;
+ }
+
+ public void Search()
+ {
+ foreach (UdpClient s in sockets)
+ {
+ try
+ {
+ Search(s);
+ }
+ catch
+ {
+ // Ignore any search errors
+ }
+ }
+ }
+
+ void Search(UdpClient client)
+ {
+ nextSearch = DateTime.Now.AddSeconds(SearchPeriod);
+ byte[] data = DiscoverDeviceMessage.EncodeSSDP();
+
+ // UDP is unreliable, so send 3 requests at a time (per Upnp spec, sec 1.1.2)
+ for (int i = 0; i < 3; i++)
+ client.Send(data, data.Length, searchEndpoint);
+ }
+
+ public IPEndPoint SearchEndpoint
+ {
+ get { return searchEndpoint; }
+ }
+
+ public void Handle(IPAddress localAddress, UpnpDeviceInfo deviceInfo, IPEndPoint endpoint)
+ {
+ // No matter what, this method should never throw an exception. If something goes wrong
+ // we should still be in a position to handle the next reply correctly.
+ try
+ {
+ /* For UPnP Port Mapping we need ot find either WANPPPConnection or WANIPConnection.
+ Any other device type is no good to us for this purpose. See the IGP overview paper
+ page 5 for an overview of device types and their hierarchy.
+ http://upnp.org/specs/gw/UPnP-gw-InternetGatewayDevice-v1-Device.pdf */
+
+ /* TODO: Currently we are assuming version 1 of the protocol. We should figure out which
+ version it is and apply the correct URN. */
+
+ /* Some routers don't correctly implement the version ID on the URN, so we only search for the type
+ prefix. */
+
+ // We have an internet gateway device now
+ UpnpNatDevice d = new UpnpNatDevice(localAddress, deviceInfo, endpoint, string.Empty);
+
+ if (devices.Contains(d))
+ {
+ // We already have found this device, so we just refresh it to let people know it's
+ // Still alive. If a device doesn't respond to a search, we dump it.
+ devices[devices.IndexOf(d)].LastSeen = DateTime.Now;
+ }
+ else
+ {
+
+ // If we send 3 requests at a time, ensure we only fetch the services list once
+ // even if three responses are received
+ if (lastFetched.ContainsKey(endpoint.Address))
+ {
+ DateTime last = lastFetched[endpoint.Address];
+ if ((DateTime.Now - last) < TimeSpan.FromSeconds(20))
+ return;
+ }
+ lastFetched[endpoint.Address] = DateTime.Now;
+
+ // Once we've parsed the information we need, we tell the device to retrieve it's service list
+ // Once we successfully receive the service list, the callback provided will be invoked.
+ NatUtility.Log("Fetching service list: {0}", d.HostEndPoint);
+ d.GetServicesList(DeviceSetupComplete);
+ }
+ }
+ catch (Exception ex)
+ {
+ NatUtility.Log("Unhandled exception when trying to decode a device's response Send me the following data: ");
+ NatUtility.Log("ErrorMessage:");
+ NatUtility.Log(ex.Message);
+ }
+ }
+
+ public void Handle(IPAddress localAddress, byte[] response, IPEndPoint endpoint)
+ {
+ // Convert it to a string for easy parsing
+ string dataString = null;
+
+ // No matter what, this method should never throw an exception. If something goes wrong
+ // we should still be in a position to handle the next reply correctly.
+ try {
+ string urn;
+ dataString = Encoding.UTF8.GetString(response);
+
+ if (NatUtility.Verbose)
+ NatUtility.Log("UPnP Response: {0}", dataString);
+
+ /* For UPnP Port Mapping we need ot find either WANPPPConnection or WANIPConnection.
+ Any other device type is no good to us for this purpose. See the IGP overview paper
+ page 5 for an overview of device types and their hierarchy.
+ http://upnp.org/specs/gw/UPnP-gw-InternetGatewayDevice-v1-Device.pdf */
+
+ /* TODO: Currently we are assuming version 1 of the protocol. We should figure out which
+ version it is and apply the correct URN. */
+
+ /* Some routers don't correctly implement the version ID on the URN, so we only search for the type
+ prefix. */
+
+ string log = "UPnP Response: Router advertised a '{0}' service";
+ StringComparison c = StringComparison.OrdinalIgnoreCase;
+ if (dataString.IndexOf("urn:schemas-upnp-org:service:WANIPConnection:", c) != -1) {
+ urn = "urn:schemas-upnp-org:service:WANIPConnection:1";
+ NatUtility.Log(log, "urn:schemas-upnp-org:service:WANIPConnection:1");
+ } else if (dataString.IndexOf("urn:schemas-upnp-org:service:WANPPPConnection:", c) != -1) {
+ urn = "urn:schemas-upnp-org:service:WANPPPConnection:1";
+ NatUtility.Log(log, "urn:schemas-upnp-org:service:WANPPPConnection:");
+ } else
+ return;
+
+ // We have an internet gateway device now
+ UpnpNatDevice d = new UpnpNatDevice(localAddress, dataString, urn);
+
+ if (devices.Contains(d))
+ {
+ // We already have found this device, so we just refresh it to let people know it's
+ // Still alive. If a device doesn't respond to a search, we dump it.
+ devices[devices.IndexOf(d)].LastSeen = DateTime.Now;
+ }
+ else
+ {
+
+ // If we send 3 requests at a time, ensure we only fetch the services list once
+ // even if three responses are received
+ if (lastFetched.ContainsKey(endpoint.Address))
+ {
+ DateTime last = lastFetched[endpoint.Address];
+ if ((DateTime.Now - last) < TimeSpan.FromSeconds(20))
+ return;
+ }
+ lastFetched[endpoint.Address] = DateTime.Now;
+
+ // Once we've parsed the information we need, we tell the device to retrieve it's service list
+ // Once we successfully receive the service list, the callback provided will be invoked.
+ NatUtility.Log("Fetching service list: {0}", d.HostEndPoint);
+ d.GetServicesList(DeviceSetupComplete);
+ }
+ }
+ catch (Exception ex)
+ {
+ Trace.WriteLine("Unhandled exception when trying to decode a device's response Send me the following data: ");
+ Trace.WriteLine("ErrorMessage:");
+ Trace.WriteLine(ex.Message);
+ Trace.WriteLine("Data string:");
+ Trace.WriteLine(dataString);
+ }
+ }
+
+ public DateTime NextSearch
+ {
+ get { return nextSearch; }
+ }
+
+ private void DeviceSetupComplete(INatDevice device)
+ {
+ lock (this.devices)
+ {
+ // We don't want the same device in there twice
+ if (devices.Contains(device))
+ return;
+
+ devices.Add(device);
+ }
+
+ OnDeviceFound(new DeviceEventArgs(device));
+ }
+
+ private void OnDeviceFound(DeviceEventArgs args)
+ {
+ if (DeviceFound != null)
+ DeviceFound(this, args);
+ }
+
+ public NatProtocol Protocol
+ {
+ get { return NatProtocol.Upnp; }
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Upnp.cs b/Mono.Nat/Upnp/Upnp.cs
new file mode 100644
index 000000000..e44a51c24
--- /dev/null
+++ b/Mono.Nat/Upnp/Upnp.cs
@@ -0,0 +1,83 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+// Ben Motmans <ben.motmans@gmail.com>
+// Nicholas Terry <nick.i.terry@gmail.com>
+//
+// Copyright (C) 2006 Alan McGovern
+// Copyright (C) 2007 Ben Motmans
+// Copyright (C) 2014 Nicholas Terry
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Text;
+
+namespace Mono.Nat.Upnp
+{
+ internal class Upnp
+ {
+ public UpnpNatDevice Handle(IPAddress localAddress, byte[] response, IPEndPoint endpoint)
+ {
+ // Convert it to a string for easy parsing
+ string dataString = null;
+
+
+ string urn;
+ dataString = Encoding.UTF8.GetString(response);
+
+ if (NatUtility.Verbose)
+ NatUtility.Log("UPnP Response: {0}", dataString);
+
+ /* For UPnP Port Mapping we need ot find either WANPPPConnection or WANIPConnection.
+ Any other device type is no good to us for this purpose. See the IGP overview paper
+ page 5 for an overview of device types and their hierarchy.
+ http://upnp.org/specs/gw/UPnP-gw-InternetGatewayDevice-v1-Device.pdf */
+
+ /* TODO: Currently we are assuming version 1 of the protocol. We should figure out which
+ version it is and apply the correct URN. */
+
+ /* Some routers don't correctly implement the version ID on the URN, so we only search for the type
+ prefix. */
+
+ string log = "UPnP Response: Router advertised a '{0}' service";
+ StringComparison c = StringComparison.OrdinalIgnoreCase;
+ if (dataString.IndexOf("urn:schemas-upnp-org:service:WANIPConnection:", c) != -1)
+ {
+ urn = "urn:schemas-upnp-org:service:WANIPConnection:1";
+ NatUtility.Log(log, "urn:schemas-upnp-org:service:WANIPConnection:1");
+ }
+ else if (dataString.IndexOf("urn:schemas-upnp-org:service:WANPPPConnection:", c) != -1)
+ {
+ urn = "urn:schemas-upnp-org:service:WANPPPConnection:1";
+ NatUtility.Log(log, "urn:schemas-upnp-org:service:WANPPPConnection:");
+ }
+ else
+ throw new NotSupportedException("Received non-supported device type");
+
+ // We have an internet gateway device now
+ return new UpnpNatDevice(localAddress, dataString, urn);
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/UpnpNatDevice.cs b/Mono.Nat/Upnp/UpnpNatDevice.cs
new file mode 100644
index 000000000..1160d3ac2
--- /dev/null
+++ b/Mono.Nat/Upnp/UpnpNatDevice.cs
@@ -0,0 +1,651 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+// Ben Motmans <ben.motmans@gmail.com>
+//
+// Copyright (C) 2006 Alan McGovern
+// Copyright (C) 2007 Ben Motmans
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.IO;
+using System.Net;
+using System.Xml;
+using System.Text;
+using System.Diagnostics;
+using MediaBrowser.Controller.Dlna;
+
+namespace Mono.Nat.Upnp
+{
+ public sealed class UpnpNatDevice : AbstractNatDevice, IEquatable<UpnpNatDevice>
+ {
+ private EndPoint hostEndPoint;
+ private IPAddress localAddress;
+ private string serviceDescriptionUrl;
+ private string controlUrl;
+ private string serviceType;
+
+ public override IPAddress LocalAddress
+ {
+ get { return localAddress; }
+ }
+
+ /// <summary>
+ /// The callback to invoke when we are finished setting up the device
+ /// </summary>
+ private NatDeviceCallback callback;
+
+ internal UpnpNatDevice(IPAddress localAddress, UpnpDeviceInfo deviceInfo, IPEndPoint hostEndPoint, string serviceType)
+ {
+ this.LastSeen = DateTime.Now;
+ this.localAddress = localAddress;
+
+ // Split the string at the "location" section so i can extract the ipaddress and service description url
+ string locationDetails = deviceInfo.Location.ToString();
+ this.serviceType = serviceType;
+
+ // Make sure we have no excess whitespace
+ locationDetails = locationDetails.Trim();
+
+ // FIXME: Is this reliable enough. What if we get a hostname as opposed to a proper http address
+ // Are we going to get addresses with the "http://" attached?
+ if (locationDetails.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase))
+ {
+ NatUtility.Log("Found device at: {0}", locationDetails);
+ // This bit strings out the "http://" from the string
+ locationDetails = locationDetails.Substring(7);
+
+ this.hostEndPoint = hostEndPoint;
+
+ NatUtility.Log("Parsed device as: {0}", this.hostEndPoint.ToString());
+
+ // The service description URL is the remainder of the "locationDetails" string. The bit that was originally after the ip
+ // and port information
+ this.serviceDescriptionUrl = locationDetails.Substring(locationDetails.IndexOf('/'));
+ }
+ else
+ {
+ NatUtility.Log("Couldn't decode address. Please send following string to the developer: ");
+ }
+ }
+
+ internal UpnpNatDevice (IPAddress localAddress, string deviceDetails, string serviceType)
+ {
+ this.LastSeen = DateTime.Now;
+ this.localAddress = localAddress;
+
+ // Split the string at the "location" section so i can extract the ipaddress and service description url
+ string locationDetails = deviceDetails.Substring(deviceDetails.IndexOf("Location", StringComparison.InvariantCultureIgnoreCase) + 9).Split('\r')[0];
+ this.serviceType = serviceType;
+
+ // Make sure we have no excess whitespace
+ locationDetails = locationDetails.Trim();
+
+ // FIXME: Is this reliable enough. What if we get a hostname as opposed to a proper http address
+ // Are we going to get addresses with the "http://" attached?
+ if (locationDetails.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase))
+ {
+ NatUtility.Log("Found device at: {0}", locationDetails);
+ // This bit strings out the "http://" from the string
+ locationDetails = locationDetails.Substring(7);
+
+ // We then split off the end of the string to get something like: 192.168.0.3:241 in our string
+ string hostAddressAndPort = locationDetails.Remove(locationDetails.IndexOf('/'));
+
+ // From this we parse out the IP address and Port
+ if (hostAddressAndPort.IndexOf(':') > 0)
+ {
+ this.hostEndPoint = new IPEndPoint(IPAddress.Parse(hostAddressAndPort.Remove(hostAddressAndPort.IndexOf(':'))),
+ Convert.ToUInt16(hostAddressAndPort.Substring(hostAddressAndPort.IndexOf(':') + 1), System.Globalization.CultureInfo.InvariantCulture));
+ }
+ else
+ {
+ // there is no port specified, use default port (80)
+ this.hostEndPoint = new IPEndPoint(IPAddress.Parse(hostAddressAndPort), 80);
+ }
+
+ NatUtility.Log("Parsed device as: {0}", this.hostEndPoint.ToString());
+
+ // The service description URL is the remainder of the "locationDetails" string. The bit that was originally after the ip
+ // and port information
+ this.serviceDescriptionUrl = locationDetails.Substring(locationDetails.IndexOf('/'));
+ }
+ else
+ {
+ Trace.WriteLine("Couldn't decode address. Please send following string to the developer: ");
+ Trace.WriteLine(deviceDetails);
+ }
+ }
+
+ /// <summary>
+ /// The EndPoint that the device is at
+ /// </summary>
+ internal EndPoint HostEndPoint
+ {
+ get { return this.hostEndPoint; }
+ }
+
+ /// <summary>
+ /// The relative url of the xml file that describes the list of services is at
+ /// </summary>
+ internal string ServiceDescriptionUrl
+ {
+ get { return this.serviceDescriptionUrl; }
+ }
+
+ /// <summary>
+ /// The relative url that we can use to control the port forwarding
+ /// </summary>
+ internal string ControlUrl
+ {
+ get { return this.controlUrl; }
+ }
+
+ /// <summary>
+ /// The service type we're using on the device
+ /// </summary>
+ public string ServiceType
+ {
+ get { return serviceType; }
+ }
+
+ /// <summary>
+ /// Begins an async call to get the external ip address of the router
+ /// </summary>
+ public override IAsyncResult BeginGetExternalIP(AsyncCallback callback, object asyncState)
+ {
+ // Create the port map message
+ GetExternalIPAddressMessage message = new GetExternalIPAddressMessage(this);
+ return BeginMessageInternal(message, callback, asyncState, EndGetExternalIPInternal);
+ }
+
+ /// <summary>
+ /// Maps the specified port to this computer
+ /// </summary>
+ public override IAsyncResult BeginCreatePortMap(Mapping mapping, AsyncCallback callback, object asyncState)
+ {
+ CreatePortMappingMessage message = new CreatePortMappingMessage(mapping, localAddress, this);
+ return BeginMessageInternal(message, callback, asyncState, EndCreatePortMapInternal);
+ }
+
+ /// <summary>
+ /// Removes a port mapping from this computer
+ /// </summary>
+ public override IAsyncResult BeginDeletePortMap(Mapping mapping, AsyncCallback callback, object asyncState)
+ {
+ DeletePortMappingMessage message = new DeletePortMappingMessage(mapping, this);
+ return BeginMessageInternal(message, callback, asyncState, EndDeletePortMapInternal);
+ }
+
+
+ public override IAsyncResult BeginGetAllMappings(AsyncCallback callback, object asyncState)
+ {
+ GetGenericPortMappingEntry message = new GetGenericPortMappingEntry(0, this);
+ return BeginMessageInternal(message, callback, asyncState, EndGetAllMappingsInternal);
+ }
+
+
+ public override IAsyncResult BeginGetSpecificMapping (Protocol protocol, int port, AsyncCallback callback, object asyncState)
+ {
+ GetSpecificPortMappingEntryMessage message = new GetSpecificPortMappingEntryMessage(protocol, port, this);
+ return this.BeginMessageInternal(message, callback, asyncState, new AsyncCallback(this.EndGetSpecificMappingInternal));
+ }
+
+ /// <summary>
+ ///
+ /// </summary>
+ /// <param name="result"></param>
+ public override void EndCreatePortMap(IAsyncResult result)
+ {
+ if (result == null) throw new ArgumentNullException("result");
+
+ PortMapAsyncResult mappingResult = result as PortMapAsyncResult;
+ if (mappingResult == null)
+ throw new ArgumentException("Invalid AsyncResult", "result");
+
+ // Check if we need to wait for the operation to finish
+ if (!result.IsCompleted)
+ result.AsyncWaitHandle.WaitOne();
+
+ // If we have a saved exception, it means something went wrong during the mapping
+ // so we just rethrow the exception and let the user figure out what they should do.
+ if (mappingResult.SavedMessage is ErrorMessage)
+ {
+ ErrorMessage msg = mappingResult.SavedMessage as ErrorMessage;
+ throw new MappingException(msg.ErrorCode, msg.Description);
+ }
+
+ //return result.AsyncState as Mapping;
+ }
+
+
+ /// <summary>
+ ///
+ /// </summary>
+ /// <param name="result"></param>
+ public override void EndDeletePortMap(IAsyncResult result)
+ {
+ if (result == null)
+ throw new ArgumentNullException("result");
+
+ PortMapAsyncResult mappingResult = result as PortMapAsyncResult;
+ if (mappingResult == null)
+ throw new ArgumentException("Invalid AsyncResult", "result");
+
+ // Check if we need to wait for the operation to finish
+ if (!mappingResult.IsCompleted)
+ mappingResult.AsyncWaitHandle.WaitOne();
+
+ // If we have a saved exception, it means something went wrong during the mapping
+ // so we just rethrow the exception and let the user figure out what they should do.
+ if (mappingResult.SavedMessage is ErrorMessage)
+ {
+ ErrorMessage msg = mappingResult.SavedMessage as ErrorMessage;
+ throw new MappingException(msg.ErrorCode, msg.Description);
+ }
+
+ // If all goes well, we just return
+ //return true;
+ }
+
+
+ public override Mapping[] EndGetAllMappings(IAsyncResult result)
+ {
+ if (result == null)
+ throw new ArgumentNullException("result");
+
+ GetAllMappingsAsyncResult mappingResult = result as GetAllMappingsAsyncResult;
+ if (mappingResult == null)
+ throw new ArgumentException("Invalid AsyncResult", "result");
+
+ if (!mappingResult.IsCompleted)
+ mappingResult.AsyncWaitHandle.WaitOne();
+
+ if (mappingResult.SavedMessage is ErrorMessage)
+ {
+ ErrorMessage msg = mappingResult.SavedMessage as ErrorMessage;
+ if (msg.ErrorCode != 713)
+ throw new MappingException(msg.ErrorCode, msg.Description);
+ }
+
+ return mappingResult.Mappings.ToArray();
+ }
+
+
+ /// <summary>
+ /// Ends an async request to get the external ip address of the router
+ /// </summary>
+ public override IPAddress EndGetExternalIP(IAsyncResult result)
+ {
+ if (result == null) throw new ArgumentNullException("result");
+
+ PortMapAsyncResult mappingResult = result as PortMapAsyncResult;
+ if (mappingResult == null)
+ throw new ArgumentException("Invalid AsyncResult", "result");
+
+ if (!result.IsCompleted)
+ result.AsyncWaitHandle.WaitOne();
+
+ if (mappingResult.SavedMessage is ErrorMessage)
+ {
+ ErrorMessage msg = mappingResult.SavedMessage as ErrorMessage;
+ throw new MappingException(msg.ErrorCode, msg.Description);
+ }
+
+ if (mappingResult.SavedMessage == null)
+ return null;
+ else
+ return ((GetExternalIPAddressResponseMessage)mappingResult.SavedMessage).ExternalIPAddress;
+ }
+
+
+ public override Mapping EndGetSpecificMapping(IAsyncResult result)
+ {
+ if (result == null)
+ throw new ArgumentNullException("result");
+
+ GetAllMappingsAsyncResult mappingResult = result as GetAllMappingsAsyncResult;
+ if (mappingResult == null)
+ throw new ArgumentException("Invalid AsyncResult", "result");
+
+ if (!mappingResult.IsCompleted)
+ mappingResult.AsyncWaitHandle.WaitOne();
+
+ if (mappingResult.SavedMessage is ErrorMessage)
+ {
+ ErrorMessage message = mappingResult.SavedMessage as ErrorMessage;
+ if (message.ErrorCode != 0x2ca)
+ {
+ throw new MappingException(message.ErrorCode, message.Description);
+ }
+ }
+ if (mappingResult.Mappings.Count == 0)
+ return new Mapping (Protocol.Tcp, -1, -1);
+
+ return mappingResult.Mappings[0];
+ }
+
+
+ public override bool Equals(object obj)
+ {
+ UpnpNatDevice device = obj as UpnpNatDevice;
+ return (device == null) ? false : this.Equals((device));
+ }
+
+
+ public bool Equals(UpnpNatDevice other)
+ {
+ return (other == null) ? false : (this.hostEndPoint.Equals(other.hostEndPoint)
+ //&& this.controlUrl == other.controlUrl
+ && this.serviceDescriptionUrl == other.serviceDescriptionUrl);
+ }
+
+ public override int GetHashCode()
+ {
+ return (this.hostEndPoint.GetHashCode() ^ this.controlUrl.GetHashCode() ^ this.serviceDescriptionUrl.GetHashCode());
+ }
+
+ private IAsyncResult BeginMessageInternal(MessageBase message, AsyncCallback storedCallback, object asyncState, AsyncCallback callback)
+ {
+ byte[] body;
+ WebRequest request = message.Encode(out body);
+ PortMapAsyncResult mappingResult = PortMapAsyncResult.Create(message, request, storedCallback, asyncState);
+
+ if (body.Length > 0)
+ {
+ request.ContentLength = body.Length;
+ request.BeginGetRequestStream(delegate(IAsyncResult result) {
+ try
+ {
+ Stream s = request.EndGetRequestStream(result);
+ s.Write(body, 0, body.Length);
+ request.BeginGetResponse(callback, mappingResult);
+ }
+ catch (Exception ex)
+ {
+ mappingResult.Complete(ex);
+ }
+ }, null);
+ }
+ else
+ {
+ request.BeginGetResponse(callback, mappingResult);
+ }
+ return mappingResult;
+ }
+
+ private void CompleteMessage(IAsyncResult result)
+ {
+ PortMapAsyncResult mappingResult = result.AsyncState as PortMapAsyncResult;
+ mappingResult.CompletedSynchronously = result.CompletedSynchronously;
+ mappingResult.Complete();
+ }
+
+ private MessageBase DecodeMessageFromResponse(Stream s, long length)
+ {
+ StringBuilder data = new StringBuilder();
+ int bytesRead = 0;
+ int totalBytesRead = 0;
+ byte[] buffer = new byte[10240];
+
+ // Read out the content of the message, hopefully picking everything up in the case where we have no contentlength
+ if (length != -1)
+ {
+ while (totalBytesRead < length)
+ {
+ bytesRead = s.Read(buffer, 0, buffer.Length);
+ data.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead));
+ totalBytesRead += bytesRead;
+ }
+ }
+ else
+ {
+ while ((bytesRead = s.Read(buffer, 0, buffer.Length)) != 0)
+ data.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead));
+ }
+
+ // Once we have our content, we need to see what kind of message it is. It'll either a an error
+ // or a response based on the action we performed.
+ return MessageBase.Decode(this, data.ToString());
+ }
+
+ private void EndCreatePortMapInternal(IAsyncResult result)
+ {
+ EndMessageInternal(result);
+ CompleteMessage(result);
+ }
+
+ private void EndMessageInternal(IAsyncResult result)
+ {
+ HttpWebResponse response = null;
+ PortMapAsyncResult mappingResult = result.AsyncState as PortMapAsyncResult;
+
+ try
+ {
+ try
+ {
+ response = (HttpWebResponse)mappingResult.Request.EndGetResponse(result);
+ }
+ catch (WebException ex)
+ {
+ // Even if the request "failed" i want to continue on to read out the response from the router
+ response = ex.Response as HttpWebResponse;
+ if (response == null)
+ mappingResult.SavedMessage = new ErrorMessage((int)ex.Status, ex.Message);
+ }
+ if (response != null)
+ mappingResult.SavedMessage = DecodeMessageFromResponse(response.GetResponseStream(), response.ContentLength);
+ }
+
+ finally
+ {
+ if (response != null)
+ response.Close();
+ }
+ }
+
+ private void EndDeletePortMapInternal(IAsyncResult result)
+ {
+ EndMessageInternal(result);
+ CompleteMessage(result);
+ }
+
+ private void EndGetAllMappingsInternal(IAsyncResult result)
+ {
+ EndMessageInternal(result);
+
+ GetAllMappingsAsyncResult mappingResult = result.AsyncState as GetAllMappingsAsyncResult;
+ GetGenericPortMappingEntryResponseMessage message = mappingResult.SavedMessage as GetGenericPortMappingEntryResponseMessage;
+ if (message != null)
+ {
+ Mapping mapping = new Mapping (message.Protocol, message.InternalPort, message.ExternalPort, message.LeaseDuration);
+ mapping.Description = message.PortMappingDescription;
+ mappingResult.Mappings.Add(mapping);
+ GetGenericPortMappingEntry next = new GetGenericPortMappingEntry(mappingResult.Mappings.Count, this);
+
+ // It's ok to do this synchronously because we should already be on anther thread
+ // and this won't block the user.
+ byte[] body;
+ WebRequest request = next.Encode(out body);
+ if (body.Length > 0)
+ {
+ request.ContentLength = body.Length;
+ request.GetRequestStream().Write(body, 0, body.Length);
+ }
+ mappingResult.Request = request;
+ request.BeginGetResponse(EndGetAllMappingsInternal, mappingResult);
+ return;
+ }
+
+ CompleteMessage(result);
+ }
+
+ private void EndGetExternalIPInternal(IAsyncResult result)
+ {
+ EndMessageInternal(result);
+ CompleteMessage(result);
+ }
+
+ private void EndGetSpecificMappingInternal(IAsyncResult result)
+ {
+ EndMessageInternal(result);
+
+ GetAllMappingsAsyncResult mappingResult = result.AsyncState as GetAllMappingsAsyncResult;
+ GetGenericPortMappingEntryResponseMessage message = mappingResult.SavedMessage as GetGenericPortMappingEntryResponseMessage;
+ if (message != null) {
+ Mapping mapping = new Mapping(mappingResult.SpecificMapping.Protocol, message.InternalPort, mappingResult.SpecificMapping.PublicPort, message.LeaseDuration);
+ mapping.Description = mappingResult.SpecificMapping.Description;
+ mappingResult.Mappings.Add(mapping);
+ }
+
+ CompleteMessage(result);
+ }
+
+ internal void GetServicesList(NatDeviceCallback callback)
+ {
+ // Save the callback so i can use it again later when i've finished parsing the services available
+ this.callback = callback;
+
+ // Create a HTTPWebRequest to download the list of services the device offers
+ byte[] body;
+ WebRequest request = new GetServicesMessage(this.serviceDescriptionUrl, this.hostEndPoint).Encode(out body);
+ if (body.Length > 0)
+ NatUtility.Log("Error: Services Message contained a body");
+ request.BeginGetResponse(this.ServicesReceived, request);
+ }
+
+ private void ServicesReceived(IAsyncResult result)
+ {
+ HttpWebResponse response = null;
+ try
+ {
+ int abortCount = 0;
+ int bytesRead = 0;
+ byte[] buffer = new byte[10240];
+ StringBuilder servicesXml = new StringBuilder();
+ XmlDocument xmldoc = new XmlDocument();
+ HttpWebRequest request = result.AsyncState as HttpWebRequest;
+ response = request.EndGetResponse(result) as HttpWebResponse;
+ Stream s = response.GetResponseStream();
+
+ if (response.StatusCode != HttpStatusCode.OK) {
+ NatUtility.Log("{0}: Couldn't get services list: {1}", HostEndPoint, response.StatusCode);
+ return; // FIXME: This the best thing to do??
+ }
+
+ while (true)
+ {
+ bytesRead = s.Read(buffer, 0, buffer.Length);
+ servicesXml.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead));
+ try
+ {
+ xmldoc.LoadXml(servicesXml.ToString());
+ response.Close();
+ break;
+ }
+ catch (XmlException)
+ {
+ // If we can't receive the entire XML within 500ms, then drop the connection
+ // Unfortunately not all routers supply a valid ContentLength (mine doesn't)
+ // so this hack is needed to keep testing our recieved data until it gets successfully
+ // parsed by the xmldoc. Without this, the code will never pick up my router.
+ if (abortCount++ > 50)
+ {
+ response.Close();
+ return;
+ }
+ NatUtility.Log("{0}: Couldn't parse services list", HostEndPoint);
+ System.Threading.Thread.Sleep(10);
+ }
+ }
+
+ NatUtility.Log("{0}: Parsed services list", HostEndPoint);
+ XmlNamespaceManager ns = new XmlNamespaceManager(xmldoc.NameTable);
+ ns.AddNamespace("ns", "urn:schemas-upnp-org:device-1-0");
+ XmlNodeList nodes = xmldoc.SelectNodes("//*/ns:serviceList", ns);
+
+ foreach (XmlNode node in nodes)
+ {
+ //Go through each service there
+ foreach (XmlNode service in node.ChildNodes)
+ {
+ //If the service is a WANIPConnection, then we have what we want
+ string type = service["serviceType"].InnerText;
+ NatUtility.Log("{0}: Found service: {1}", HostEndPoint, type);
+ StringComparison c = StringComparison.OrdinalIgnoreCase;
+ // TODO: Add support for version 2 of UPnP.
+ if (type.Equals("urn:schemas-upnp-org:service:WANPPPConnection:1", c) ||
+ type.Equals("urn:schemas-upnp-org:service:WANIPConnection:1", c))
+ {
+ this.controlUrl = service["controlURL"].InnerText;
+ NatUtility.Log("{0}: Found upnp service at: {1}", HostEndPoint, controlUrl);
+ try
+ {
+ Uri u = new Uri(controlUrl);
+ if (u.IsAbsoluteUri)
+ {
+ EndPoint old = hostEndPoint;
+ this.hostEndPoint = new IPEndPoint(IPAddress.Parse(u.Host), u.Port);
+ NatUtility.Log("{0}: Absolute URI detected. Host address is now: {1}", old, HostEndPoint);
+ this.controlUrl = controlUrl.Substring(u.GetLeftPart(UriPartial.Authority).Length);
+ NatUtility.Log("{0}: New control url: {1}", HostEndPoint, controlUrl);
+ }
+ }
+ catch
+ {
+ NatUtility.Log("{0}: Assuming control Uri is relative: {1}", HostEndPoint, controlUrl);
+ }
+ NatUtility.Log("{0}: Handshake Complete", HostEndPoint);
+ this.callback(this);
+ return;
+ }
+ }
+ }
+
+ //If we get here, it means that we didn't get WANIPConnection service, which means no uPnP forwarding
+ //So we don't invoke the callback, so this device is never added to our lists
+ }
+ catch (WebException ex)
+ {
+ // Just drop the connection, FIXME: Should i retry?
+ NatUtility.Log("{0}: Device denied the connection attempt: {1}", HostEndPoint, ex);
+ }
+ finally
+ {
+ if (response != null)
+ response.Close();
+ }
+ }
+
+ /// <summary>
+ /// Overridden.
+ /// </summary>
+ /// <returns></returns>
+ public override string ToString( )
+ {
+ //GetExternalIP is blocking and can throw exceptions, can't use it here.
+ return String.Format(
+ "UpnpNatDevice - EndPoint: {0}, External IP: {1}, Control Url: {2}, Service Description Url: {3}, Service Type: {4}, Last Seen: {5}",
+ this.hostEndPoint, "Manually Check" /*this.GetExternalIP()*/, this.controlUrl, this.serviceDescriptionUrl, this.serviceType, this.LastSeen);
+ }
+ }
+} \ No newline at end of file
diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec
index ff52aebce..1259a759b 100644
--- a/Nuget/MediaBrowser.Common.Internal.nuspec
+++ b/Nuget/MediaBrowser.Common.Internal.nuspec
@@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
<metadata>
<id>MediaBrowser.Common.Internal</id>
- <version>3.0.658</version>
+ <version>3.0.662</version>
<title>MediaBrowser.Common.Internal</title>
<authors>Luke</authors>
<owners>ebr,Luke,scottisafool</owners>
@@ -12,9 +12,9 @@
<description>Contains common components shared by Emby Theater and Emby Server. Not intended for plugin developer consumption.</description>
<copyright>Copyright © Emby 2013</copyright>
<dependencies>
- <dependency id="MediaBrowser.Common" version="3.0.658" />
- <dependency id="NLog" version="4.3.6" />
- <dependency id="SimpleInjector" version="3.2.0" />
+ <dependency id="MediaBrowser.Common" version="3.0.662" />
+ <dependency id="NLog" version="4.3.8" />
+ <dependency id="SimpleInjector" version="3.2.2" />
</dependencies>
</metadata>
<files>
diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec
index 822246cf8..931db69fa 100644
--- a/Nuget/MediaBrowser.Common.nuspec
+++ b/Nuget/MediaBrowser.Common.nuspec
@@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
<metadata>
<id>MediaBrowser.Common</id>
- <version>3.0.658</version>
+ <version>3.0.662</version>
<title>MediaBrowser.Common</title>
<authors>Emby Team</authors>
<owners>ebr,Luke,scottisafool</owners>
diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec
index 6b8e2aa65..b3d929f59 100644
--- a/Nuget/MediaBrowser.Server.Core.nuspec
+++ b/Nuget/MediaBrowser.Server.Core.nuspec
@@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>MediaBrowser.Server.Core</id>
- <version>3.0.658</version>
+ <version>3.0.662</version>
<title>Media Browser.Server.Core</title>
<authors>Emby Team</authors>
<owners>ebr,Luke,scottisafool</owners>
@@ -12,7 +12,7 @@
<description>Contains core components required to build plugins for Emby Server.</description>
<copyright>Copyright © Emby 2013</copyright>
<dependencies>
- <dependency id="MediaBrowser.Common" version="3.0.658" />
+ <dependency id="MediaBrowser.Common" version="3.0.662" />
<dependency id="Interfaces.IO" version="1.0.0.5" />
</dependencies>
</metadata>
diff --git a/OpenSubtitlesHandler/Utilities.cs b/OpenSubtitlesHandler/Utilities.cs
index d26c76b7c..9a90462f6 100644
--- a/OpenSubtitlesHandler/Utilities.cs
+++ b/OpenSubtitlesHandler/Utilities.cs
@@ -201,7 +201,8 @@ namespace OpenSubtitlesHandler
// Response parsing will fail with this enabled
EnableHttpCompression = false,
- CancellationToken = cancellationToken
+ CancellationToken = cancellationToken,
+ BufferContent = false
};
if (string.IsNullOrEmpty(options.UserAgent))